recce-nightly 1.3.0.20250507__py3-none-any.whl → 1.4.0.20250514__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.

Files changed (93) hide show
  1. recce/VERSION +1 -1
  2. recce/__init__.py +22 -22
  3. recce/adapter/base.py +11 -14
  4. recce/adapter/dbt_adapter/__init__.py +355 -316
  5. recce/adapter/dbt_adapter/dbt_version.py +3 -0
  6. recce/adapter/sqlmesh_adapter.py +24 -35
  7. recce/apis/check_api.py +39 -28
  8. recce/apis/check_func.py +33 -27
  9. recce/apis/run_api.py +25 -19
  10. recce/apis/run_func.py +29 -23
  11. recce/artifact.py +44 -49
  12. recce/cli.py +484 -285
  13. recce/config.py +42 -33
  14. recce/core.py +52 -44
  15. recce/data/404.html +1 -1
  16. recce/data/_next/static/chunks/{368-7587b306577df275.js → 778-aef312bffb4c0312.js} +15 -15
  17. recce/data/_next/static/chunks/8d700b6a.ed11a130057c7a47.js +1 -0
  18. recce/data/_next/static/chunks/app/layout-c713a2829d3279e4.js +1 -0
  19. recce/data/_next/static/chunks/app/page-7086764277331fcb.js +1 -0
  20. recce/data/_next/static/chunks/{cd9f8d63-cf0d5a7b0f7a92e8.js → cd9f8d63-e020f408095ed77c.js} +3 -3
  21. recce/data/_next/static/chunks/webpack-b787cb1a4f2293de.js +1 -0
  22. recce/data/_next/static/css/88b8abc134cfd59a.css +3 -0
  23. recce/data/index.html +2 -2
  24. recce/data/index.txt +2 -2
  25. recce/diff.py +6 -12
  26. recce/event/__init__.py +74 -72
  27. recce/event/collector.py +27 -20
  28. recce/event/track.py +39 -27
  29. recce/exceptions.py +1 -1
  30. recce/git.py +7 -7
  31. recce/github.py +57 -53
  32. recce/models/__init__.py +1 -1
  33. recce/models/check.py +6 -7
  34. recce/models/run.py +1 -0
  35. recce/models/types.py +27 -27
  36. recce/pull_request.py +26 -24
  37. recce/run.py +148 -111
  38. recce/server.py +103 -89
  39. recce/state.py +209 -177
  40. recce/summary.py +168 -143
  41. recce/tasks/__init__.py +3 -3
  42. recce/tasks/core.py +11 -13
  43. recce/tasks/dataframe.py +19 -17
  44. recce/tasks/histogram.py +69 -34
  45. recce/tasks/lineage.py +2 -2
  46. recce/tasks/profile.py +147 -86
  47. recce/tasks/query.py +139 -87
  48. recce/tasks/rowcount.py +33 -30
  49. recce/tasks/schema.py +14 -14
  50. recce/tasks/top_k.py +35 -35
  51. recce/tasks/valuediff.py +216 -152
  52. recce/util/breaking.py +77 -84
  53. recce/util/cll.py +55 -51
  54. recce/util/io.py +19 -17
  55. recce/util/logger.py +1 -1
  56. recce/util/recce_cloud.py +70 -72
  57. recce/util/singleton.py +4 -4
  58. recce/yaml/__init__.py +7 -10
  59. {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/METADATA +5 -2
  60. recce_nightly-1.4.0.20250514.dist-info/RECORD +143 -0
  61. {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/WHEEL +1 -1
  62. tests/adapter/dbt_adapter/conftest.py +1 -0
  63. tests/adapter/dbt_adapter/dbt_test_helper.py +28 -18
  64. tests/adapter/dbt_adapter/test_dbt_adapter.py +0 -15
  65. tests/adapter/dbt_adapter/test_dbt_cll.py +39 -32
  66. tests/adapter/dbt_adapter/test_selector.py +22 -21
  67. tests/tasks/test_histogram.py +58 -66
  68. tests/tasks/test_lineage.py +36 -23
  69. tests/tasks/test_preset_checks.py +45 -31
  70. tests/tasks/test_profile.py +340 -15
  71. tests/tasks/test_query.py +40 -40
  72. tests/tasks/test_row_count.py +65 -46
  73. tests/tasks/test_schema.py +65 -42
  74. tests/tasks/test_top_k.py +22 -18
  75. tests/tasks/test_valuediff.py +43 -32
  76. tests/test_cli.py +71 -58
  77. tests/test_config.py +7 -9
  78. tests/test_core.py +5 -3
  79. tests/test_dbt.py +7 -7
  80. tests/test_pull_request.py +1 -1
  81. tests/test_server.py +19 -13
  82. tests/test_state.py +40 -27
  83. tests/test_summary.py +18 -14
  84. recce/data/_next/static/chunks/8d700b6a-f0b1f6b9e0d97ce2.js +0 -1
  85. recce/data/_next/static/chunks/app/layout-9102e22cb73f74d6.js +0 -1
  86. recce/data/_next/static/chunks/app/page-92f13c8fad9fae3d.js +0 -1
  87. recce/data/_next/static/chunks/webpack-567d72f0bc0820d5.js +0 -1
  88. recce_nightly-1.3.0.20250507.dist-info/RECORD +0 -142
  89. /recce/data/_next/static/{K5iKlCYhdcpq8Ea6ck9J_ → E_HPXsXdrqHg2YEHmU3mK}/_buildManifest.js +0 -0
  90. /recce/data/_next/static/{K5iKlCYhdcpq8Ea6ck9J_ → E_HPXsXdrqHg2YEHmU3mK}/_ssgManifest.js +0 -0
  91. {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/entry_points.txt +0 -0
  92. {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.dist-info}/licenses/LICENSE +0 -0
  93. {recce_nightly-1.3.0.20250507.dist-info → recce_nightly-1.4.0.20250514.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 = 'recce.yml'
9
- RECCE_PRESET_CHECK_COMMENT = '''Preset Checks
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 = 'recce_error.log'
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, 'r') as f:
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'Recce config file not found. Generating default config file at \'{self.config_file}\'')
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('checks'):
36
+ if not self.config.get("checks"):
37
37
  return
38
38
 
39
- for check in self.config['checks']:
39
+ for check in self.config["checks"]:
40
40
  try:
41
- check_type = check.get('type')
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 == 'lineage_diff':
44
+ if check_type == "lineage_diff":
45
45
  from recce.tasks.lineage import LineageDiffCheckValidator
46
+
46
47
  validator = LineageDiffCheckValidator()
47
- elif check_type == 'schema_diff':
48
+ elif check_type == "schema_diff":
48
49
  from recce.tasks.schema import SchemaDiffCheckValidator
50
+
49
51
  validator = SchemaDiffCheckValidator()
50
- elif check_type == 'row_count_diff':
52
+ elif check_type == "row_count_diff":
51
53
  from recce.tasks.rowcount import RowCountDiffCheckValidator
54
+
52
55
  validator = RowCountDiffCheckValidator()
53
- elif check_type == 'query':
56
+ elif check_type == "query":
54
57
  from recce.tasks.query import QueryCheckValidator
58
+
55
59
  validator = QueryCheckValidator()
56
- elif check_type == 'query_diff':
60
+ elif check_type == "query_diff":
57
61
  from recce.tasks.query import QueryDiffCheckValidator
62
+
58
63
  validator = QueryDiffCheckValidator()
59
- elif check_type == 'value_diff' or check_type == 'value_diff_detail':
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 == 'profile_diff':
68
+ elif check_type == "profile_diff":
63
69
  from recce.tasks.profile import ProfileCheckValidator
70
+
64
71
  validator = ProfileCheckValidator()
65
- elif check_type == 'top_k_diff':
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 == 'histogram_diff':
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
- cause=e)
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
- checks=yaml.CommentedSeq())
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='Row count diff',
88
- description='Check the row count diff for all table models.',
89
- type='row_count_diff',
90
- params={'select': 'state:modified,config.materialized:table'},
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='Schema diff',
94
- description='Check the schema diff for all nodes.',
95
- type='schema_diff',
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['checks'].append(check)
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, 'w') as f:
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, List, Tuple, Set
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 RecceState, RecceStateMetadata, GitRepoInfo, PullRequestInfo, RecceStateLoader
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('uvicorn')
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('state_loader')
29
- is_review_mode = kwargs.get('review', False)
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('sqlmesh', False):
38
- logger.warning('SQLMesh adapter is still in EXPERIMENTAL mode.')
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
- context.adapter_type = 'sqlmesh'
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
- context.adapter_type = 'dbt'
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('The state file is required for review mode')
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['nodes'].items():
80
- if excluded_types and node.get('resource_type') in excluded_types:
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['name']] = unique_id
83
- for unique_id, node in base['nodes'].items():
84
- if excluded_types and node.get('resource_type') in excluded_types:
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['name']] = unique_id
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('RECCE_PR_URL'))
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 == 'merge':
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 == 'revert':
168
+ elif method == "revert":
161
169
  self.state_loader.refresh()
162
170
  self.import_state(self.state_loader.state, merge=False)
163
- elif method == 'overwrite':
171
+ elif method == "overwrite":
164
172
  state = self.export_state()
165
173
  self.state_loader.export(state)
166
174
  else:
167
- raise Exception(f'Unsupported method: {method}')
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
- 'type': str(c.type),
176
- 'params': c.params,
177
- 'view_options': c.view_options,
178
- }, sort_keys=True)
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
- _calculate_checksum(c): c for c in self.checks if c.is_preset
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('token')
265
- set_recce_cloud_onboarding_state(token, 'completed')
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'Failed to mark onboarding completed in Recce Cloud. Reason: {str(e)}')
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('sqlmesh', False):
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('DEMO', False):
293
- return 'demo'
300
+ if os.environ.get("DEMO", False):
301
+ return "demo"
294
302
 
295
303
  if not self.state_loader:
296
- return 'none'
304
+ return "none"
297
305
 
298
306
  if self.state_loader.cloud_mode:
299
- return 'cloud'
307
+ return "cloud"
300
308
  elif self.state_loader.state_file:
301
- return 'file'
309
+ return "file"
302
310
  else:
303
- return 'none'
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-567d72f0bc0820d5.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,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;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-567d72f0bc0820d5.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:I[37194,[],\"\"]\n3:I[79137,[],\"\"]\n4:I[63846,[],\"\"]\na:I[67160,[],\"\"]\n5:{\"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\"}\n6:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n7:{\"display\":\"inline-block\"}\n8:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nb:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L1\",null,{\"buildId\":\"K5iKlCYhdcpq8Ea6ck9J_\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L2\",[[\"$\",\"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,[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"suppressHydrationWarning\":true,\"children\":[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$5\",\"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\":\"$6\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$8\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$L9\"],\"globalErrorComponent\":\"$a\",\"missingSlots\":\"$Wb\"}]\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"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\"}]]\n2:null\n"])</script></body></html>
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,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;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\":\"E_HPXsXdrqHg2YEHmU3mK\",\"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>