@routinecrew/routinecode 2.31.35 → 2.31.39
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/bin/prompt-vault.js +1 -1
- package/bin/setup.js +1 -1
- package/hooks/lib/block_message.py +15 -50
- package/hooks/lib/rdpe_maturity.py +128 -0
- package/hooks/lib/rdpe_report.py +47 -0
- package/hooks/lib/rdpe_sanitize.py +90 -0
- package/hooks/lib/sync_manager.py +5 -1
- package/hooks/post-agent-return.sh +118 -4
- package/hooks/pre-agent-spawn.mjs +1 -1
- package/hooks/pre-credit-guard.sh +3 -1
- package/hooks/pre-mark-complete.sh +19 -0
- package/hooks/session-start.sh +3 -0
- package/hooks/stop-usage-metrics.sh +211 -74
- package/hooks/tests/gate-integration.test.sh +106 -0
- package/hooks/user-prompt-submit.sh +0 -12
- package/mcp-db-manager/dist/analyzers/duplicate-detector.js +1 -1
- package/mcp-db-manager/dist/core/model-parser.js +1 -1
- package/mcp-db-manager/dist/core/schema-reader.js +1 -1
- package/mcp-db-manager/dist/executors/migration-executor.js +1 -1
- package/mcp-db-manager/dist/generators/migration-generator.js +1 -1
- package/mcp-db-manager/dist/index.js +1 -1
- package/mcp-db-manager/dist/validators/schema-validator.js +1 -1
- package/mcp-dev-tester/dist/analyzers/auth-diagnoser.js +1 -1
- package/mcp-dev-tester/dist/analyzers/backend-analyzer.js +1 -1
- package/mcp-dev-tester/dist/analyzers/endpoint-diagnoser.js +1 -1
- package/mcp-dev-tester/dist/analyzers/frontend-analyzer.js +1 -1
- package/mcp-dev-tester/dist/analyzers/runtime-diagnoser.js +1 -1
- package/mcp-dev-tester/dist/analyzers/type-comparator.js +1 -1
- package/mcp-dev-tester/dist/analyzers/validation-diagnoser.js +1 -1
- package/mcp-dev-tester/dist/browser/playwright-manager.js +1 -1
- package/mcp-dev-tester/dist/browser/ui-tester.js +1 -1
- package/mcp-dev-tester/dist/config/project-config.js +1 -1
- package/mcp-dev-tester/dist/index.js +1 -1
- package/mcp-dev-tester/dist/testers/api-tester.js +1 -1
- package/mcp-doc-fetcher/dist/index.js +1 -1
- package/mcp-html-renderer/dist/index.js +1 -1
- package/mcp-html-renderer/dist/preview.js +1 -1
- package/mcp-html-renderer/dist/template-engine.js +1 -1
- package/mcp-intelligence/dist/concept-catalog.js +1 -1
- package/mcp-intelligence/dist/concept-server-proxy.js +1 -1
- package/mcp-intelligence/dist/config-auth.js +1 -1
- package/mcp-intelligence/dist/contracts.generated.js +1 -1
- package/mcp-intelligence/dist/domain-templates.js +1 -1
- package/mcp-intelligence/dist/grounding.js +1 -1
- package/mcp-intelligence/dist/handlers.js +1 -1
- package/mcp-intelligence/dist/index.js +1 -1
- package/mcp-intelligence/dist/intelligence-file.js +1 -1
- package/mcp-intelligence/dist/knowledge-critic.js +1 -1
- package/mcp-intelligence/dist/knowledge-handlers.js +1 -1
- package/mcp-intelligence/dist/mention-extractor.js +1 -1
- package/mcp-intelligence/dist/provenance.js +1 -1
- package/mcp-intelligence/dist/semantic-embedder.js +1 -1
- package/mcp-log-watcher/dist/config-auth.js +1 -1
- package/mcp-log-watcher/dist/file-watcher.js +1 -1
- package/mcp-log-watcher/dist/index.js +1 -1
- package/mcp-log-watcher/dist/known-solutions.js +1 -1
- package/mcp-log-watcher/dist/log-store.js +1 -1
- package/mcp-log-watcher/dist/websocket-server.js +1 -1
- package/mcp-plan-manager/dist/__tests__/plan-parser.test.js +1 -1
- package/mcp-plan-manager/dist/file-ops.js +1 -1
- package/mcp-plan-manager/dist/index.js +1 -1
- package/mcp-plan-manager/dist/plan-parser.js +1 -1
- package/mcp-project-context/dist/git-ops.js +1 -1
- package/mcp-project-context/dist/index.js +1 -1
- package/mcp-project-context/dist/project-analysis.js +1 -1
- package/mcp-routinecode/dist/agent-tracker.js +1 -1
- package/mcp-routinecode/dist/blast-radius.js +1 -1
- package/mcp-routinecode/dist/config-auth.js +1 -1
- package/mcp-routinecode/dist/contracts.generated.js +1 -1
- package/mcp-routinecode/dist/explorer-advisor.js +1 -1
- package/mcp-routinecode/dist/failure-tracker.js +1 -1
- package/mcp-routinecode/dist/file-lock-registry.js +1 -1
- package/mcp-routinecode/dist/file-risk-scorer.js +1 -1
- package/mcp-routinecode/dist/index.js +1 -1
- package/mcp-routinecode/dist/known-solutions.js +1 -1
- package/mcp-routinecode/dist/ledger-miner.js +1 -1
- package/mcp-routinecode/dist/observed-draft.js +1 -1
- package/mcp-routinecode/dist/pattern-matcher.js +1 -1
- package/mcp-routinecode/dist/routines.js +1 -1
- package/mcp-routinecode/dist/rule-engine.js +1 -1
- package/mcp-routinecode/dist/rule-promoter.js +1 -1
- package/mcp-routinecode/dist/telemetry-analyzer.js +1 -1
- package/mcp-routinecode/dist/verification.js +0 -0
- package/mcp-routinecode/dist/viral-loop.js +1 -1
- package/package.json +1 -1
|
@@ -43,8 +43,7 @@ transcript_path = inp.get('transcript_path', '')
|
|
|
43
43
|
if not transcript_path or not os.path.exists(transcript_path):
|
|
44
44
|
emit({"continue": True})
|
|
45
45
|
|
|
46
|
-
# ── 1. Transcript
|
|
47
|
-
last_user_idx = -1
|
|
46
|
+
# ── 1. Transcript 로드 (cursor 모델: 커서 이후 신규 구간만 델타 집계) ──
|
|
48
47
|
entries = []
|
|
49
48
|
try:
|
|
50
49
|
with open(transcript_path) as f:
|
|
@@ -61,13 +60,107 @@ except Exception:
|
|
|
61
60
|
if not entries:
|
|
62
61
|
emit({"continue": True})
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
# ── Cursor 모델 (v9): 직전 처리 커서 이후 신규 구간만 델타 집계 ──
|
|
64
|
+
# 기존 slice(last_user_idx+1:)는 매 Stop 전체 재집계로 삼각수 중복(turns 52만)을 냈다.
|
|
65
|
+
# cursor 는 세션별 "마지막 처리 entry 개수"를 저장해 각 entry 를 정확히 1회만 집계한다
|
|
66
|
+
# (append-only transcript 실측 확증: compaction 도 in-place append, 재작성 없음).
|
|
67
|
+
import hashlib as _hl
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
_cursor_dir = os.path.join(os.path.expanduser('~'), '.' + 'routinecode', 'rdpe', 'cursor')
|
|
70
|
+
_cursor_file = os.path.join(_cursor_dir, session_id + '.json')
|
|
71
|
+
_cursor = 0
|
|
72
|
+
try:
|
|
73
|
+
with open(_cursor_file) as _cf:
|
|
74
|
+
_cst = json.load(_cf)
|
|
75
|
+
_c = _cst.get('count', 0)
|
|
76
|
+
_lu = _cst.get('last_uuid')
|
|
77
|
+
# 파손/범위이탈 또는 append-only 불일치(저장 uuid != 그 위치 entry) 시 len 폴백
|
|
78
|
+
# (그 세션 1회 누락 ≪ 커서 0 재폭발). 정상이면 저장 count 사용.
|
|
79
|
+
if isinstance(_c, int) and 0 <= _c <= len(entries):
|
|
80
|
+
if _lu and _c > 0:
|
|
81
|
+
_at = entries[_c - 1].get('uuid') if _c - 1 < len(entries) else None
|
|
82
|
+
_cursor = _c if (_at == _lu or _at is None) else len(entries)
|
|
83
|
+
else:
|
|
84
|
+
_cursor = _c
|
|
85
|
+
else:
|
|
86
|
+
_cursor = len(entries)
|
|
87
|
+
except FileNotFoundError:
|
|
88
|
+
_cursor = 0 # 최초 세션
|
|
89
|
+
except Exception:
|
|
90
|
+
_cursor = len(entries) # 파손 → 누락(1회) 선택, 재폭발 회피
|
|
91
|
+
|
|
92
|
+
_new = entries[_cursor:]
|
|
93
|
+
|
|
94
|
+
# real_prompt: 실제 사용자 프롬프트만 작업 경계. tool_result·시스템 주입 태그 제외(§3).
|
|
95
|
+
_SYS_TAGS = ('<task-notification', '<local-command', '<command-name',
|
|
96
|
+
'<command-message', '<system-reminder')
|
|
97
|
+
def _is_real_prompt(_e):
|
|
98
|
+
if _e.get('type') != 'user' or _e.get('isCompactSummary'):
|
|
99
|
+
return False
|
|
100
|
+
_ct = _e.get('message', {}).get('content', '')
|
|
101
|
+
if isinstance(_ct, str):
|
|
102
|
+
_s = _ct.lstrip()
|
|
103
|
+
if not _s or 'This session is being continued' in _ct:
|
|
104
|
+
return False
|
|
105
|
+
return not any(_s.startswith(_t) for _t in _SYS_TAGS)
|
|
106
|
+
if isinstance(_ct, list):
|
|
107
|
+
_htr = any(isinstance(_b, dict) and _b.get('type') == 'tool_result' for _b in _ct)
|
|
108
|
+
_htx = False
|
|
109
|
+
for _b in _ct:
|
|
110
|
+
if not isinstance(_b, dict) or _b.get('type') != 'text':
|
|
111
|
+
continue
|
|
112
|
+
_t = (_b.get('text') or '').strip()
|
|
113
|
+
if not _t or 'This session is being continued' in _t:
|
|
114
|
+
continue
|
|
115
|
+
if any(_t.startswith(_tg) for _tg in _SYS_TAGS):
|
|
116
|
+
continue
|
|
117
|
+
_htx = True
|
|
118
|
+
return _htx if (_htr and not _htx) is False else False
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
# target_shape: 원문 미수집(A10) — 확장자/명령어명만.
|
|
122
|
+
def _target_shape(_name, _inp):
|
|
123
|
+
try:
|
|
124
|
+
if not isinstance(_inp, dict):
|
|
125
|
+
return ''
|
|
126
|
+
if _name in ('Read', 'Edit', 'Write', 'NotebookEdit', 'MultiEdit'):
|
|
127
|
+
_p = _inp.get('file_path') or _inp.get('notebook_path') or ''
|
|
128
|
+
return (os.path.splitext(_p)[1] or 'noext')
|
|
129
|
+
if _name == 'Bash':
|
|
130
|
+
import re as _re
|
|
131
|
+
for _sg in _re.split(r'&&|\||;|\n', (_inp.get('command') or '').strip()):
|
|
132
|
+
for _t in _sg.strip().split():
|
|
133
|
+
if _t == 'cd':
|
|
134
|
+
break # cd 의 인자는 경로(명령 아님) → 이 세그먼트 포기, 다음 세그먼트로
|
|
135
|
+
if _t in ('sudo', 'env', 'time', 'nohup', 'exec') or '=' in _t or _t.startswith('-'):
|
|
136
|
+
continue
|
|
137
|
+
_b = os.path.basename(_t)
|
|
138
|
+
return _b if _re.match(r'^[a-zA-Z][a-zA-Z0-9_.+-]*$', _b) else ''
|
|
139
|
+
return ''
|
|
140
|
+
if _name in ('Grep', 'Glob'):
|
|
141
|
+
return _name.lower()
|
|
142
|
+
except Exception:
|
|
143
|
+
return ''
|
|
144
|
+
return ''
|
|
145
|
+
|
|
146
|
+
def _msg_tools(_e):
|
|
147
|
+
_out = []
|
|
148
|
+
_ct = _e.get('message', {}).get('content', [])
|
|
149
|
+
if isinstance(_ct, list):
|
|
150
|
+
for _b in _ct:
|
|
151
|
+
if not isinstance(_b, dict) or _b.get('type') != 'tool_use':
|
|
152
|
+
continue
|
|
153
|
+
_nm = _b.get('name', '')
|
|
154
|
+
if not isinstance(_nm, str) or not _nm:
|
|
155
|
+
continue
|
|
156
|
+
if _nm.startswith('mcp__'):
|
|
157
|
+
_s2 = _nm.split('__')
|
|
158
|
+
_base = 'mcp:' + (_s2[1] if len(_s2) > 1 and _s2[1] else 'unknown')
|
|
159
|
+
else:
|
|
160
|
+
_base = _nm
|
|
161
|
+
_tg = _target_shape(_nm, _b.get('input', {}))
|
|
162
|
+
_out.append(_base + (':' + _tg if _tg else ''))
|
|
163
|
+
return _out
|
|
71
164
|
|
|
72
165
|
input_tokens = 0
|
|
73
166
|
output_tokens = 0
|
|
@@ -76,51 +169,67 @@ cache_create = 0
|
|
|
76
169
|
assistant_count = 0
|
|
77
170
|
mcp_calls = 0
|
|
78
171
|
mcp_calls_by_server = {}
|
|
79
|
-
|
|
80
|
-
|
|
172
|
+
turns_delta = 0
|
|
173
|
+
msg_task_sigs = {} # sig -> {count, delta_c, shape}
|
|
81
174
|
|
|
82
|
-
|
|
83
|
-
|
|
175
|
+
# 토큰·mcp: 신규 구간 전체 assistant 총합 (토큰/mcp/task 동일 _new 기준 → 불일치 0)
|
|
176
|
+
for _e in _new:
|
|
177
|
+
if _e.get('type') != 'assistant':
|
|
84
178
|
continue
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if inp_t > 0 or out_t > 0 or cr > 0 or cc > 0:
|
|
91
|
-
input_tokens += inp_t
|
|
92
|
-
output_tokens += out_t
|
|
93
|
-
cache_read += cr
|
|
94
|
-
cache_create += cc
|
|
179
|
+
_u = _e.get('message', {}).get('usage', {})
|
|
180
|
+
_it = _u.get('input_tokens', 0); _ot = _u.get('output_tokens', 0)
|
|
181
|
+
_cr = _u.get('cache_read_input_tokens', 0); _cc = _u.get('cache_creation_input_tokens', 0)
|
|
182
|
+
if _it or _ot or _cr or _cc:
|
|
183
|
+
input_tokens += _it; output_tokens += _ot; cache_read += _cr; cache_create += _cc
|
|
95
184
|
assistant_count += 1
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
185
|
+
_ct2 = _e.get('message', {}).get('content', [])
|
|
186
|
+
if isinstance(_ct2, list):
|
|
187
|
+
for _b in _ct2:
|
|
188
|
+
if isinstance(_b, dict) and _b.get('type') == 'tool_use':
|
|
189
|
+
_nm = _b.get('name', '')
|
|
190
|
+
if isinstance(_nm, str) and _nm.startswith('mcp__'):
|
|
191
|
+
mcp_calls += 1
|
|
192
|
+
_s2 = _nm.split('__')
|
|
193
|
+
_sv = _s2[1] if len(_s2) > 1 and _s2[1] else 'unknown'
|
|
194
|
+
mcp_calls_by_server[_sv] = mcp_calls_by_server.get(_sv, 0) + 1
|
|
195
|
+
|
|
196
|
+
# turns + task_sig: real_prompt 경계로 작업 분할 (queuing 다중 프롬프트 각각 1작업)
|
|
197
|
+
_pidx = [_i for _i, _e in enumerate(_new) if _is_real_prompt(_e)]
|
|
198
|
+
for _k, _p in enumerate(_pidx):
|
|
199
|
+
turns_delta += 1
|
|
200
|
+
_end = _pidx[_k + 1] if _k + 1 < len(_pidx) else len(_new)
|
|
201
|
+
_tools = []
|
|
202
|
+
_seg_cc = 0
|
|
203
|
+
for _se in _new[_p:_end]:
|
|
204
|
+
if _se.get('type') != 'assistant':
|
|
205
|
+
continue
|
|
206
|
+
_seg_cc += _se.get('message', {}).get('usage', {}).get('cache_creation_input_tokens', 0) or 0
|
|
207
|
+
_tools.extend(_msg_tools(_se))
|
|
208
|
+
_col = []
|
|
209
|
+
for _t in _tools:
|
|
210
|
+
if not _col or _col[-1] != _t:
|
|
211
|
+
_col.append(_t)
|
|
212
|
+
if not _col:
|
|
213
|
+
continue # 도구 없는 작업(순수 대화) → sig 없음
|
|
214
|
+
_shape = '|'.join('>'.join(_col[_i:_i + 3]) for _i in range(len(_col) - 2)) if len(_col) >= 3 else '>'.join(_col)
|
|
215
|
+
_sig = _hl.sha1(_shape.encode('utf-8', 'replace')).hexdigest()[:12]
|
|
216
|
+
_d = msg_task_sigs.setdefault(_sig, {"count": 0, "delta_c": 0, "shape": _shape[:80]})
|
|
217
|
+
_d["count"] += 1
|
|
218
|
+
_d["delta_c"] += int(_seg_cc)
|
|
120
219
|
|
|
121
220
|
total_tokens = input_tokens + output_tokens + cache_read + cache_create
|
|
122
221
|
|
|
123
|
-
if total_tokens == 0:
|
|
222
|
+
if total_tokens == 0 and turns_delta == 0 and not msg_task_sigs:
|
|
223
|
+
# 신규 구간에 집계할 토큰·작업·sig 전무(degenerate/aborted 세션) → 종료.
|
|
224
|
+
# 단 커서는 전진시켜 다음 Stop 재읽기(중복) 방지. ★reviewer 지적: 기존엔 토큰만
|
|
225
|
+
# 0이면 turns/sig 가 있어도 여기서 종료돼 cursor 미전진 → degenerate 44파일 turns 누락.
|
|
226
|
+
try:
|
|
227
|
+
os.makedirs(_cursor_dir, exist_ok=True)
|
|
228
|
+
with open(_cursor_file, "w") as _cf:
|
|
229
|
+
json.dump({"count": len(entries),
|
|
230
|
+
"last_uuid": (entries[-1].get("uuid") if entries else None)}, _cf)
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
124
233
|
emit({"continue": True})
|
|
125
234
|
|
|
126
235
|
now = datetime.now(timezone.utc)
|
|
@@ -175,7 +284,8 @@ end_event = {
|
|
|
175
284
|
"cwd": os.path.basename(cwd),
|
|
176
285
|
"metadata": {
|
|
177
286
|
"duration_sec": duration_sec,
|
|
178
|
-
|
|
287
|
+
# cursor 모델: 이번 신규 구간의 작업(=real_prompt) 수. 전체 재계산 아님 → 중복 0.
|
|
288
|
+
"total_turns": turns_delta,
|
|
179
289
|
}
|
|
180
290
|
}
|
|
181
291
|
# N4·조건 6·11: fail-open append
|
|
@@ -185,30 +295,23 @@ try:
|
|
|
185
295
|
except Exception:
|
|
186
296
|
pass
|
|
187
297
|
|
|
188
|
-
# ── 2.6. task 이벤트
|
|
189
|
-
#
|
|
298
|
+
# ── 2.6. task 이벤트 서버 sync (cursor 블록이 산출한 msg_task_sigs 사용) ──
|
|
299
|
+
# sig·ΔC·turns 는 위 cursor 블록에서 신규 구간 기준으로 이미 계산됨(각 entry 1회, 중복 0).
|
|
300
|
+
# 여기선 서버 전송용 대표 task 이벤트 1건만 emit (과다 전송 방지).
|
|
190
301
|
try:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# 3-gram 시퀀스를 정규화해 sig 산출 (연속 중복 접기).
|
|
194
|
-
collapsed = []
|
|
195
|
-
for t in tool_seq:
|
|
196
|
-
if not collapsed or collapsed[-1] != t:
|
|
197
|
-
collapsed.append(t)
|
|
198
|
-
ngrams = [tuple(collapsed[i:i+3]) for i in range(max(1, len(collapsed) - 2))]
|
|
199
|
-
seq_repr = '|'.join('>'.join(g) for g in ngrams)
|
|
200
|
-
task_sig = _hl.sha1(seq_repr.encode('utf-8', 'replace')).hexdigest()[:12]
|
|
302
|
+
if msg_task_sigs:
|
|
303
|
+
_top_sig = max(msg_task_sigs.items(), key=lambda kv: kv[1]["delta_c"])[0]
|
|
201
304
|
cwd_hash = _hl.sha1(os.path.basename(cwd).encode('utf-8', 'replace')).hexdigest()[:8]
|
|
202
305
|
_lib = os.path.join(os.environ.get('ROUTINECODE_HOOKS_DIR', ''), 'lib')
|
|
203
306
|
if _lib and _lib not in sys.path:
|
|
204
307
|
sys.path.insert(0, _lib)
|
|
205
308
|
try:
|
|
206
309
|
from emit_raw import emit_raw as _emit
|
|
207
|
-
_emit('task', {'task_sig':
|
|
310
|
+
_emit('task', {'task_sig': _top_sig, 'cwd_hash': cwd_hash}, session=session_id, at=now.isoformat())
|
|
208
311
|
except Exception:
|
|
209
312
|
# emit_raw 미가용 시 직접 append (동일 봉투)
|
|
210
313
|
_te = {"at": now.isoformat(), "date": today, "session": session_id, "type": "task",
|
|
211
|
-
"value": 1, "metadata": {"task_sig":
|
|
314
|
+
"value": 1, "metadata": {"task_sig": _top_sig, "cwd_hash": cwd_hash}}
|
|
212
315
|
with open(os.path.join(log_dir, "events.jsonl"), "a") as f:
|
|
213
316
|
f.write(json.dumps(_te, ensure_ascii=False) + "\n")
|
|
214
317
|
except Exception:
|
|
@@ -235,6 +338,9 @@ day_data["tokens"] = tok
|
|
|
235
338
|
# MCP 호출 일별 집계 (총수 + 서버별)
|
|
236
339
|
day_data["mcp_calls"] = day_data.get("mcp_calls", 0) + mcp_calls
|
|
237
340
|
mcp_srv = day_data.get("mcp_calls_by_server", {})
|
|
341
|
+
# (P2 full-recompute 제거 — cursor 블록이 mcp_calls/mcp_calls_by_server 를 신규 구간
|
|
342
|
+
# 기준으로 단일 경로 계산하므로 event/daily 불일치 원천 소멸.)
|
|
343
|
+
|
|
238
344
|
for srv, cnt in mcp_calls_by_server.items():
|
|
239
345
|
mcp_srv[srv] = mcp_srv.get(srv, 0) + cnt
|
|
240
346
|
day_data["mcp_calls_by_server"] = mcp_srv
|
|
@@ -248,19 +354,25 @@ try:
|
|
|
248
354
|
if session_id and session_id not in _sess:
|
|
249
355
|
_sess.append(session_id)
|
|
250
356
|
day_data["sessions"] = _sess
|
|
251
|
-
#
|
|
252
|
-
# 반복
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
# ΔC는 engine context-monitor(OpenCode용·미사용)가 아니라 여기서 이미 파싱된
|
|
256
|
-
# cache_create 로 수집한다 — Claude Code 환경의 실제 경로(engine 배선 불필요).
|
|
357
|
+
# RDPE: per-message task_sigs 를 daily 에 병합 — events.jsonl 회전 무관 영속.
|
|
358
|
+
# occurrence_count(반복) + ΔC(cache_creation) 교차세션 입력. ★O1/O2 수정으로 세션
|
|
359
|
+
# 전체 메시지의 sig·ΔC 가 쌓인다(기존엔 마지막 턴만 봐서 영구 0이었음).
|
|
360
|
+
if msg_task_sigs:
|
|
257
361
|
_tsigs = day_data.get("task_sigs", {})
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
362
|
+
for _sg, _dd in msg_task_sigs.items():
|
|
363
|
+
_prev = _tsigs.get(_sg, {"count": 0, "delta_c": 0, "sessions": [], "shape": ""})
|
|
364
|
+
if isinstance(_prev, int): # 구 포맷(int) 하위호환 마이그레이션
|
|
365
|
+
_prev = {"count": _prev, "delta_c": 0, "sessions": [], "shape": ""}
|
|
366
|
+
if not _prev.get("shape"): # 도구 조합 원문 1회 보존
|
|
367
|
+
_prev["shape"] = _dd.get("shape", "")
|
|
368
|
+
_prev["count"] = _prev.get("count", 0) + _dd["count"]
|
|
369
|
+
_prev["delta_c"] = _prev.get("delta_c", 0) + _dd["delta_c"]
|
|
370
|
+
# distinct_sessions 가드(§5 크로스세션): sig 를 관측한 세션 id 집합.
|
|
371
|
+
_ss = _prev.get("sessions", [])
|
|
372
|
+
if session_id and session_id not in _ss:
|
|
373
|
+
_ss.append(session_id)
|
|
374
|
+
_prev["sessions"] = _ss
|
|
375
|
+
_tsigs[_sg] = _prev
|
|
264
376
|
day_data["task_sigs"] = _tsigs
|
|
265
377
|
except Exception:
|
|
266
378
|
pass
|
|
@@ -274,6 +386,31 @@ try:
|
|
|
274
386
|
except Exception:
|
|
275
387
|
pass
|
|
276
388
|
|
|
389
|
+
# cursor 전진: 처리한 마지막 entry 까지 기록 → 다음 Stop 은 이 이후만 집계(중복 0).
|
|
390
|
+
# 세션별 파일(동시쓰기 경쟁 회피). last_uuid 는 append-only 재동기 앵커(§8-3).
|
|
391
|
+
try:
|
|
392
|
+
os.makedirs(_cursor_dir, exist_ok=True)
|
|
393
|
+
with open(_cursor_file, "w") as _cf:
|
|
394
|
+
json.dump({"count": len(entries),
|
|
395
|
+
"last_uuid": (entries[-1].get("uuid") if entries else None)}, _cf)
|
|
396
|
+
except Exception:
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
# ── 3.5. RDPE 성숙 갱신 (daily 갱신 직후, 추가 read 0) ──
|
|
400
|
+
# daily.task_sigs → routines.json 감지→성숙(§5). 로컬 완결(서버 미사용). fail-open.
|
|
401
|
+
try:
|
|
402
|
+
_rlib = os.path.join(os.environ.get('ROUTINECODE_HOOKS_DIR', ''), 'lib')
|
|
403
|
+
if _rlib and _rlib not in sys.path:
|
|
404
|
+
sys.path.insert(0, _rlib)
|
|
405
|
+
import rdpe_maturity as _rm
|
|
406
|
+
_routines = _rm.build_routines(daily)
|
|
407
|
+
_rdir = os.path.join(os.path.expanduser('~'), '.' + 'routinecode', 'rdpe')
|
|
408
|
+
os.makedirs(_rdir, exist_ok=True)
|
|
409
|
+
with open(os.path.join(_rdir, 'routines.json'), 'w') as _rf:
|
|
410
|
+
json.dump(_routines, _rf, ensure_ascii=False, indent=2)
|
|
411
|
+
except Exception:
|
|
412
|
+
pass
|
|
413
|
+
|
|
277
414
|
# ── 4. Usage sync (세션 종료 시 서버 동기화) ──
|
|
278
415
|
# 블로킹 방지: sync_manager.py를 detached 자식 프로세스로 실행하고 즉시 반환.
|
|
279
416
|
# 네트워크 timeout/재시도는 자식에서 처리되며, stop hook 본체는 로컬 기록 후 종료.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Gate integration tests — 오케스트레이션 게이트 실측 회귀 (설계 W8)
|
|
3
|
+
#
|
|
4
|
+
# 검증 대상:
|
|
5
|
+
# [1-2] 3-strike: escalation 상태 ↔ pre-edit-check.sh 차단 (경로 계약)
|
|
6
|
+
# [2b] 3-strike: 컴파일된 tracker(dist)가 훅과 동일 경로 상수를 쓰는지
|
|
7
|
+
# [3-6] design-gate(.sources/pre-agent-spawn.mjs.src): block/score/light/degraded
|
|
8
|
+
# [7] credit-guard: 검증 도구 무과금 (FREE_TOOLS)
|
|
9
|
+
# [8] mark-complete: MCP 미연결 degraded(고지 후 생략) + 연결 시 fail-closed 유지
|
|
10
|
+
#
|
|
11
|
+
# 전부 격리 HOME/XDG — 실사용 상태 무오염. 실행: bash hooks/tests/gate-integration.test.sh
|
|
12
|
+
set -u
|
|
13
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
HOOKS="$(cd "$HERE/.." && pwd)"
|
|
15
|
+
ROOT="$(cd "$HOOKS/.." && pwd)"
|
|
16
|
+
|
|
17
|
+
pass=0; fail=0
|
|
18
|
+
ok() { echo " ✓ $1"; pass=$((pass+1)); }
|
|
19
|
+
ng() { echo " ✗ $1"; fail=$((fail+1)); }
|
|
20
|
+
assert_contains() { echo "$1" | grep -q "$2" && ok "$3" || ng "$3 — got: $(echo "$1" | head -c 140)"; }
|
|
21
|
+
assert_not_contains() { echo "$1" | grep -q "$2" && ng "$3 — got: $(echo "$1" | head -c 140)" || ok "$3"; }
|
|
22
|
+
|
|
23
|
+
TH=$(mktemp -d)
|
|
24
|
+
export XDG_RUNTIME_DIR=""
|
|
25
|
+
RUN="$TH/.routinecode/run"
|
|
26
|
+
KNOW="$TH/.routinecode/knowledge"
|
|
27
|
+
mkdir -p "$RUN" "$KNOW/verification" "$KNOW/classifications"
|
|
28
|
+
|
|
29
|
+
# design-gate는 소스 오브 트루스(.src)를 복사해 직접 실행 (obfuscated 산출물은 publish 시 재생성)
|
|
30
|
+
GATE_MJS="$TH/gate.mjs"
|
|
31
|
+
cp "$ROOT/.sources/pre-agent-spawn.mjs.src" "$GATE_MJS"
|
|
32
|
+
|
|
33
|
+
IMPL_PAYLOAD='{"tool_name":"Agent","tool_input":{"subagent_type":"routinecode:implementor","description":"w8","prompt":"TASK: x\nCONTEXT:\n- taskId: sim-w8\n- planName: none"}}'
|
|
34
|
+
EDIT_PAYLOAD='{"tool_name":"Edit","tool_input":{"file_path":"/tmp/x.ts"}}'
|
|
35
|
+
|
|
36
|
+
echo "[1] 3-strike: 통일 경로의 escalation → Edit 차단"
|
|
37
|
+
echo '{"escalated":true,"issueId":"w8"}' > "$RUN/routinecode-escalation.json"
|
|
38
|
+
OUT=$(echo "$EDIT_PAYLOAD" | HOME="$TH" bash "$HOOKS/pre-edit-check.sh" 2>/dev/null)
|
|
39
|
+
assert_contains "$OUT" '"block"' "escalation 활성 시 Edit block"
|
|
40
|
+
|
|
41
|
+
echo "[2] 3-strike: escalation 없음 → allow"
|
|
42
|
+
rm -f "$RUN/routinecode-escalation.json"
|
|
43
|
+
OUT=$(echo "$EDIT_PAYLOAD" | HOME="$TH" bash "$HOOKS/pre-edit-check.sh" 2>/dev/null)
|
|
44
|
+
assert_contains "$OUT" 'allow' "비에스컬레이션 시 allow"
|
|
45
|
+
|
|
46
|
+
echo "[2b] 3-strike: 컴파일된 tracker가 훅과 동일 경로 상수 사용 (쓰기=읽기 계약)"
|
|
47
|
+
DIST="$ROOT/mcp-routinecode/dist/failure-tracker.js"
|
|
48
|
+
if [ -f "$DIST" ]; then
|
|
49
|
+
if grep -q "XDG_RUNTIME_DIR" "$DIST" && grep -q "routinecode-escalation.json" "$DIST" \
|
|
50
|
+
&& grep -q "'run'" "$DIST"; then
|
|
51
|
+
ok "dist tracker가 RC_RUNDIR 해석식 포함 (/tmp 하드코딩 아님)"
|
|
52
|
+
else
|
|
53
|
+
ng "dist tracker에 RC_RUNDIR 해석식 없음 — 재빌드 필요 또는 회귀"
|
|
54
|
+
fi
|
|
55
|
+
else
|
|
56
|
+
echo " - SKIP (mcp-routinecode/dist 미빌드)"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo "[3] design-gate: mcp-alive 有 + 검증파일 無 → block (fail-closed 유지)"
|
|
60
|
+
echo "$(date +%s)" > "$RUN/mcp-alive"
|
|
61
|
+
OUT=$(echo "$IMPL_PAYLOAD" | HOME="$TH" node "$GATE_MJS" 2>/dev/null)
|
|
62
|
+
assert_contains "$OUT" 'No design verification' "무검증 implementor block"
|
|
63
|
+
|
|
64
|
+
echo "[4] design-gate: score 95 → allow"
|
|
65
|
+
echo '{"taskId":"sim-w8","phase":"design","currentScore":95}' > "$KNOW/verification/design-sim-w8.json"
|
|
66
|
+
OUT=$(echo "$IMPL_PAYLOAD" | HOME="$TH" node "$GATE_MJS" 2>/dev/null)
|
|
67
|
+
assert_contains "$OUT" 'allow' "score>=90 allow"
|
|
68
|
+
assert_not_contains "$OUT" '"block"' "score>=90 비차단"
|
|
69
|
+
rm -f "$KNOW/verification/design-sim-w8.json"
|
|
70
|
+
|
|
71
|
+
echo "[5] design-gate: tier=light → allow (면제)"
|
|
72
|
+
echo '{"tier":"light"}' > "$KNOW/classifications/sim-w8.json"
|
|
73
|
+
OUT=$(echo "$IMPL_PAYLOAD" | HOME="$TH" node "$GATE_MJS" 2>/dev/null)
|
|
74
|
+
assert_contains "$OUT" 'allow' "light tier 면제"
|
|
75
|
+
rm -f "$KNOW/classifications/sim-w8.json"
|
|
76
|
+
|
|
77
|
+
echo "[6] design-gate: mcp-alive 無 → degraded(allow+고지)"
|
|
78
|
+
rm -f "$RUN/mcp-alive"
|
|
79
|
+
OUT=$(echo "$IMPL_PAYLOAD" | HOME="$TH" node "$GATE_MJS" 2>/dev/null)
|
|
80
|
+
assert_contains "$OUT" 'allow' "MCP 미연결 시 allow"
|
|
81
|
+
assert_contains "$OUT" 'MCP 미연결' "degraded 고지 포함"
|
|
82
|
+
|
|
83
|
+
echo "[7] credit-guard: score_verification 무과금 통과"
|
|
84
|
+
OUT=$(echo '{"tool_name":"mcp__routinecode__score_verification","tool_input":{}}' \
|
|
85
|
+
| HOME="$TH" ROUTINECODE_USER_ID=1 ROUTINECODE_TENANT_ID=1 bash "$HOOKS/pre-credit-guard.sh" 2>/dev/null)
|
|
86
|
+
RC=$?
|
|
87
|
+
if [ "$RC" -eq 0 ]; then
|
|
88
|
+
assert_not_contains "$OUT" '"block"' "FREE 도구 비차단(exit 0)"
|
|
89
|
+
else
|
|
90
|
+
ng "credit-guard exit=$RC (FREE여야 0)"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
echo "[8] mark-complete: mcp-alive 無 + BUILD unknown → degraded(고지, 비차단)"
|
|
94
|
+
OUT=$(echo '{}' | HOME="$TH" bash "$HOOKS/pre-mark-complete.sh" 2>/dev/null)
|
|
95
|
+
assert_contains "$OUT" 'MCP 미연결' "완료게이트 degraded 고지"
|
|
96
|
+
assert_not_contains "$OUT" '"block"' "완료게이트 비차단"
|
|
97
|
+
|
|
98
|
+
echo "[8b] mark-complete: mcp-alive 有 + BUILD unknown → 기존 fail-closed block 유지"
|
|
99
|
+
echo "$(date +%s)" > "$RUN/mcp-alive"
|
|
100
|
+
OUT=$(echo '{}' | HOME="$TH" bash "$HOOKS/pre-mark-complete.sh" 2>/dev/null)
|
|
101
|
+
assert_contains "$OUT" '"block"' "연결 세션 게이트 무약화"
|
|
102
|
+
|
|
103
|
+
rm -rf "$TH"
|
|
104
|
+
echo ""
|
|
105
|
+
echo "pass=$pass fail=$fail"
|
|
106
|
+
[ "$fail" -eq 0 ] && exit 0 || exit 1
|
|
@@ -69,18 +69,6 @@ try:
|
|
|
69
69
|
msg = block_message.generate()
|
|
70
70
|
except Exception:
|
|
71
71
|
msg = '[RoutineCode] Daily free Credits exhausted. Write one line at https://routinecode.routinecrew.net/recharge/write for instant +50, or run /share.'
|
|
72
|
-
try:
|
|
73
|
-
from i18n import get_locale
|
|
74
|
-
loc = get_locale()
|
|
75
|
-
except Exception:
|
|
76
|
-
loc = 'en'
|
|
77
|
-
_foot = {
|
|
78
|
-
'ko': '\n\n(이 창만 멈춥니다 — 따로 연 claude 세션은 영향받지 않습니다.)',
|
|
79
|
-
'ja': '\n\n(この画面のみ停止します — 別途開いた claude セッションには影響しません。)',
|
|
80
|
-
'zh': '\n\n(仅此窗口暂停 — 另开的 claude 会话不受影响。)',
|
|
81
|
-
'es': '\n\n(Solo esta ventana se pausa — otras sesiones de claude no se ven afectadas.)',
|
|
82
|
-
}
|
|
83
|
-
msg = msg + _foot.get(loc, '\n\n(Only this window is paused — any separate claude session is unaffected.)')
|
|
84
72
|
# UserPromptSubmit: {"decision":"block","reason":...} DISPLAYS reason to the user
|
|
85
73
|
# (continue:false does NOT — it only shows the generic 'Operation stopped by hook').
|
|
86
74
|
print(json.dumps({'decision': 'block', 'reason': msg}))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const a0_0x3a828b=a0_0x4c93;function a0_0x4c93(_0x2aaf96,_0xd77b79){_0x2aaf96=_0x2aaf96-(0x80f+0x1*0x211+-0x831);const _0x4da514=a0_0x2555();let _0x537982=_0x4da514[_0x2aaf96];if(a0_0x4c93['DKgleS']===undefined){var _0x207dd8=function(_0x287cbc){const _0x53dc6e='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x320cee='',_0x278e5a='';for(let _0x27e28d=0x1753+-0x19c+-0x6d*0x33,_0x1b70b6,_0x18757d,_0x5d7934=0x1*-0x1fd3+-0x7*-0xfb+0x18f6;_0x18757d=_0x287cbc['charAt'](_0x5d7934++);~_0x18757d&&(_0x1b70b6=_0x27e28d%(-0x1fc+0x90d*0x2+-0x101a)?_0x1b70b6*(0x214b+0xeb7+-0x1*0x2fc2)+_0x18757d:_0x18757d,_0x27e28d++%(0x1*-0x1e0b+-0x2603*0x1+0x4412))?_0x320cee+=String['fromCharCode'](0x59*-0x1d+-0x1134+0x1c48&_0x1b70b6>>(-(-0x285+-0x1920+0x1ba7)*_0x27e28d&-0x189e+0xd2d+-0x5*-0x24b)):-0x24aa+-0x2003+0x44ad){_0x18757d=_0x53dc6e['indexOf'](_0x18757d);}for(let _0x32bd32=0xc0a+0x1b7*-0x6+-0x1c0,_0xfe1861=_0x320cee['length'];_0x32bd32<_0xfe1861;_0x32bd32++){_0x278e5a+='%'+('00'+_0x320cee['charCodeAt'](_0x32bd32)['toString'](-0x1*0x745+-0x22ad+0x2a02))['slice'](-(0xf50+-0x1701+0x7b3));}return decodeURIComponent(_0x278e5a);};const _0x4591d9=function(_0x347f3f,_0x308aa9){let _0x2b2993=[],_0x1d272b=0x1*-0x40f+-0x1a*0x166+0x286b*0x1,_0x276d24,_0x448059='';_0x347f3f=_0x207dd8(_0x347f3f);let _0xf366d3;for(_0xf366d3=0x670+0x76d*0x1+-0xddd;_0xf366d3<-0x87f+-0x1*-0x1445+-0xac6;_0xf366d3++){_0x2b2993[_0xf366d3]=_0xf366d3;}for(_0xf366d3=0x1*0x150a+0xbd1*-0x2+-0x4*-0xa6;_0xf366d3<0x4*0x3ce+-0x211+-0xc27;_0xf366d3++){_0x1d272b=(_0x1d272b+_0x2b2993[_0xf366d3]+_0x308aa9['charCodeAt'](_0xf366d3%_0x308aa9['length']))%(-0x6*-0x257+0x1*-0x1b7f+0xe75),_0x276d24=_0x2b2993[_0xf366d3],_0x2b2993[_0xf366d3]=_0x2b2993[_0x1d272b],_0x2b2993[_0x1d272b]=_0x276d24;}_0xf366d3=-0xe6a+0x1e63+-0xff9,_0x1d272b=-0x101d*0x1+-0x17a1+-0x13df*-0x2;for(let _0x28623b=0x1817+0xf2f*-0x1+-0x8e8;_0x28623b<_0x347f3f['length'];_0x28623b++){_0xf366d3=(_0xf366d3+(0x42c*0x6+-0x1b0d+0x206*0x1))%(0x20a2+0x1e6+-0x94*0x3a),_0x1d272b=(_0x1d272b+_0x2b2993[_0xf366d3])%(-0x115+-0xc0b+-0x20*-0x71),_0x276d24=_0x2b2993[_0xf366d3],_0x2b2993[_0xf366d3]=_0x2b2993[_0x1d272b],_0x2b2993[_0x1d272b]=_0x276d24,_0x448059+=String['fromCharCode'](_0x347f3f['charCodeAt'](_0x28623b)^_0x2b2993[(_0x2b2993[_0xf366d3]+_0x2b2993[_0x1d272b])%(0xa32+0xf39*-0x2+0x550*0x4)]);}return _0x448059;};a0_0x4c93['NCYtYD']=_0x4591d9,a0_0x4c93['QhWBdj']={},a0_0x4c93['DKgleS']=!![];}const _0x512e2e=_0x4da514[-0x26*-0x2+0x4*0x11b+-0x4b8],_0x553c93=_0x2aaf96+_0x512e2e,_0x2b9d45=a0_0x4c93['QhWBdj'][_0x553c93];return!_0x2b9d45?(a0_0x4c93['gOLYVe']===undefined&&(a0_0x4c93['gOLYVe']=!![]),_0x537982=a0_0x4c93['NCYtYD'](_0x537982,_0xd77b79),a0_0x4c93['QhWBdj'][_0x553c93]=_0x537982):_0x537982=_0x2b9d45,_0x537982;}(function(_0x56c69d,_0x56b02b){const _0x5b1b60=a0_0x4c93,_0x5e9447=_0x56c69d();while(!![]){try{const _0x405e42=-parseInt(_0x5b1b60(0x256,'5yqY'))/(-0x15*0x107+-0x18c7+-0x2e5b*-0x1)*(parseInt(_0x5b1b60(0x226,'r1qM'))/(-0x61b*-0x1+0x1abe+-0x20d7))+-parseInt(_0x5b1b60(0x1f4,'KdEe'))/(0x1fe6+0x3ad*-0x1+-0x1*0x1c36)+parseInt(_0x5b1b60(0x23a,'Ao0A'))/(0x259+-0x610+0x3bb)*(parseInt(_0x5b1b60(0x22d,'vF^0'))/(-0x2c*-0x67+-0x1*-0x770+0x6d*-0x3b))+parseInt(_0x5b1b60(0x209,'T#!%'))/(0x259d+-0x1e32+0x277*-0x3)*(-parseInt(_0x5b1b60(0x26c,'ctJ&'))/(0x90c+-0x6d*-0x37+-0x3*0xad0))+parseInt(_0x5b1b60(0x25b,'DUPK'))/(-0x15*0x143+0xb*-0x30b+0x3c00)*(-parseInt(_0x5b1b60(0x24d,'77yF'))/(0x582+-0xf3c+0x7*0x165))+-parseInt(_0x5b1b60(0x20d,'V7%@'))/(0x2*0x8e6+0x153e+-0x2700)*(-parseInt(_0x5b1b60(0x220,'P]Bj'))/(-0xc73+-0x25*0x4a+0x1730))+parseInt(_0x5b1b60(0x268,'8(AQ'))/(-0x1992*0x1+0x83*0x19+0xcd3)*(parseInt(_0x5b1b60(0x255,'3enR'))/(-0x3*0x3ce+0x1831*-0x1+0x8ea*0x4));if(_0x405e42===_0x56b02b)break;else _0x5e9447['push'](_0x5e9447['shift']());}catch(_0x4ef59c){_0x5e9447['push'](_0x5e9447['shift']());}}}(a0_0x2555,-0x291b9+-0x1c1e6*0x1+0x9d429*0x1));export class DuplicateDetector{[a0_0x3a828b(0x227,'A9H8')](_0x16adf7){const _0x3af66e=a0_0x3a828b,_0x1b0fbd=this[_0x3af66e(0x235,'mUk8')](_0x16adf7[_0x3af66e(0x1fb,'zJQL')]),_0x336ab1=this[_0x3af66e(0x1f3,'#OJc')](_0x16adf7[_0x3af66e(0x238,']Ayp')]),_0x1735a0=_0x1b0fbd[_0x3af66e(0x26a,'4xM4')](_0x3ae05d=>_0x3ae05d[_0x3af66e(0x1f1,'CPOi')]===_0x3af66e(0x267,'r1qM'))[_0x3af66e(0x216,'3enR')]+_0x336ab1[_0x3af66e(0x26a,'4xM4')](_0x32927e=>_0x32927e[_0x3af66e(0x24b,'We%R')]>0x1c70+0x1c9*-0xb+0x8cd*-0x1+0.8)[_0x3af66e(0x224,'We%R')];return{'fields':_0x1b0fbd,'tables':_0x336ab1,'summary':{'totalDuplicateFields':_0x1b0fbd[_0x3af66e(0x21c,'FP3^')],'totalDuplicateTables':_0x336ab1[_0x3af66e(0x258,'bLVY')],'autoFixable':_0x1735a0}};}[a0_0x3a828b(0x214,'z!y@')](_0x442ee7){const _0x9d78ba=a0_0x3a828b,_0x53ebd8=[],_0x436f21=new Map();for(const _0x51b953 of _0x442ee7){for(const _0x33bcfe of _0x51b953[_0x9d78ba(0x253,'CPOi')]){const _0x243c55=this[_0x9d78ba(0x1f8,'A9H8')](_0x33bcfe[_0x9d78ba(0x257,'NTUd')]);!_0x436f21[_0x9d78ba(0x1f2,'jrpv')](_0x243c55)&&_0x436f21[_0x9d78ba(0x23f,'z!y@')](_0x243c55,[]),_0x436f21[_0x9d78ba(0x215,'#7Ix')](_0x243c55)[_0x9d78ba(0x20b,'RZxv')](_0x51b953[_0x9d78ba(0x20a,'mUk8')]+'.'+_0x33bcfe[_0x9d78ba(0x222,'oVpx')]);}}for(const [_0x5c46de,_0x3ce4e3]of _0x436f21[_0x9d78ba(0x26f,'%C]z')]()){if(_0x3ce4e3[_0x9d78ba(0x25e,'mUk8')]<=-0xff1+-0x1*0x1697+0x2689*0x1)continue;const _0x5c0453=new Set(_0x3ce4e3[_0x9d78ba(0x211,'jrpv')](_0x283726=>_0x283726[_0x9d78ba(0x25a,'P]Bj')]('.')[-0x1dda*0x1+0x214b+-0x1*0x370])),_0xa1aa0e=_0x5c0453[_0x9d78ba(0x205,'b7@J')]===0x1*-0x1e0b+-0x2603*0x1+0x440f,_0x212bc9=_0x3ce4e3[_0x9d78ba(0x1fe,'XlqD')](_0x5cb1ec=>_0x5cb1ec[_0x9d78ba(0x22a,'oVpx')]('.')[0x59*-0x1d+-0x1134+0x1b49]);_0x53ebd8[_0x9d78ba(0x21a,'*TYt')]({'fieldName':_0x5c46de,'tables':_0x212bc9,'type':_0xa1aa0e?_0x9d78ba(0x219,'mUk8'):_0x9d78ba(0x1f5,'DUPK'),'severity':this[_0x9d78ba(0x236,'r1qM')](_0x5c46de,_0x212bc9),'recommendation':this[_0x9d78ba(0x23e,'jrpv')](_0x5c46de,_0x212bc9,_0xa1aa0e)});}return _0x53ebd8;}[a0_0x3a828b(0x245,'P]Bj')](_0x15c7a5){const _0x25382e=a0_0x3a828b,_0x4588e5=[];for(let _0x237c55=-0x285+-0x1920+0x1ba5;_0x237c55<_0x15c7a5[_0x25382e(0x259,'Ao0A')];_0x237c55++){const _0x3c299c=_0x15c7a5[_0x237c55],_0x2e3c2e=[];for(let _0x4fda63=_0x237c55+(-0x189e+0xd2d+-0x5*-0x24a);_0x4fda63<_0x15c7a5[_0x25382e(0x21c,'FP3^')];_0x4fda63++){const _0x423e74=_0x15c7a5[_0x4fda63],_0x294904=this[_0x25382e(0x23b,'XlqD')](_0x3c299c,_0x423e74);_0x294904>-0x24aa+-0x2003+0x44ad+0.6&&_0x2e3c2e[_0x25382e(0x242,'WAs%')](_0x423e74[_0x25382e(0x1f0,'zJQL')]);}_0x2e3c2e[_0x25382e(0x224,'We%R')]>0xc0a+0x1b7*-0x6+-0x1c0&&_0x4588e5[_0x25382e(0x1ef,'z!y@')]({'tableName':_0x3c299c[_0x25382e(0x248,'HdIY')],'similarTo':_0x2e3c2e,'similarity':Math[_0x25382e(0x260,'zJQL')](..._0x2e3c2e[_0x25382e(0x1fa,'m%P%')](_0xd4b7c9=>{const _0x440f6b=_0x25382e,_0x1091e7=_0x15c7a5[_0x440f6b(0x203,'%C]z')](_0x2cf072=>_0x2cf072[_0x440f6b(0x26d,'4xM4')]===_0xd4b7c9);return this[_0x440f6b(0x24e,'dRfC')](_0x3c299c,_0x1091e7);})),'recommendation':this[_0x25382e(0x21f,'3enR')](_0x3c299c,_0x2e3c2e)});}return _0x4588e5;}[a0_0x3a828b(0x261,'vF^0')](_0x209cba){const _0xc5c3eb=a0_0x3a828b;return _0x209cba[_0xc5c3eb(0x254,'DUPK')](/[-_]/g,'')[_0xc5c3eb(0x250,'NTUd')](/([A-Z])/g,_0x5157a6=>_0x5157a6[_0xc5c3eb(0x201,'(A*@')]())[_0xc5c3eb(0x1fd,'dRfC')]();}[a0_0x3a828b(0x223,'K[y@')](_0x32e35e,_0x45389f){const _0x3e2d89=a0_0x3a828b,_0x5d3e2d=this[_0x3e2d89(0x263,'Z6vv')](_0x32e35e[_0x3e2d89(0x22e,'K[y@')],_0x45389f[_0x3e2d89(0x21b,'^O[s')]),_0x5369f2=new Set(_0x32e35e[_0x3e2d89(0x1f9,'T#!%')][_0x3e2d89(0x21d,'NTUd')](_0x5c00ce=>this[_0x3e2d89(0x232,'^O[s')](_0x5c00ce[_0x3e2d89(0x202,'r1qM')]))),_0xf66331=new Set(_0x45389f[_0x3e2d89(0x246,'3enR')][_0x3e2d89(0x24f,'oVpx')](_0x11ca45=>this[_0x3e2d89(0x20e,'RZxv')](_0x11ca45[_0x3e2d89(0x208,'77yF')]))),_0x2ef79c=new Set([..._0x5369f2][_0x3e2d89(0x237,'^O[s')](_0x2c8f22=>_0xf66331[_0x3e2d89(0x234,'A9H8')](_0x2c8f22))),_0x2caac2=new Set([..._0x5369f2,..._0xf66331]),_0x569ad5=_0x2caac2[_0x3e2d89(0x252,'8(AQ')]>-0x1*0x745+-0x22ad+0x29f2?_0x2ef79c[_0x3e2d89(0x20c,'HdIY')]/_0x2caac2[_0x3e2d89(0x204,'bLVY')]:0xf50+-0x1701+0x7b1;return _0x5d3e2d*(0x1*-0x40f+-0x1a*0x166+0x286b*0x1+0.3)+_0x569ad5*(0x670+0x76d*0x1+-0xddd+0.7);}[a0_0x3a828b(0x240,'%C]z')](_0x5189c5,_0x207b7a){const _0x59173b=a0_0x3a828b,_0x5db1cd=this[_0x59173b(0x1f6,'V7%@')](_0x5189c5,_0x207b7a),_0x56f006=Math[_0x59173b(0x1f7,'yk3@')](_0x5189c5[_0x59173b(0x25f,'b7@J')],_0x207b7a[_0x59173b(0x22c,'jrpv')]);return _0x56f006>-0x87f+-0x1*-0x1445+-0xbc6?0x1*0x150a+0xbd1*-0x2+-0x7*-0x5f-_0x5db1cd/_0x56f006:0x4*0x3ce+-0x211+-0xd26;}[a0_0x3a828b(0x26e,'#7Ix')](_0x2ad9f5,_0x5e38dc){const _0x1179fc=a0_0x3a828b,_0x2ef4df=_0x2ad9f5[_0x1179fc(0x239,'#OJc')],_0x625691=_0x5e38dc[_0x1179fc(0x251,'ib$4')],_0x357a2a=Array[_0x1179fc(0x244,'RZxv')]({'length':_0x2ef4df+(-0x6*-0x257+0x1*-0x1b7f+0xd76)},()=>Array(_0x625691+(-0xe6a+0x1e63+-0xff8))[_0x1179fc(0x213,'b7@J')](-0x101d*0x1+-0x17a1+-0x13df*-0x2));for(let _0x5d3423=0x1817+0xf2f*-0x1+-0x8e8;_0x5d3423<=_0x2ef4df;_0x5d3423++)_0x357a2a[_0x5d3423][0x42c*0x6+-0x1b0d+0x205*0x1]=_0x5d3423;for(let _0x10e9cd=0x20a2+0x1e6+-0xaa*0x34;_0x10e9cd<=_0x625691;_0x10e9cd++)_0x357a2a[-0x115+-0xc0b+-0x2a*-0x50][_0x10e9cd]=_0x10e9cd;for(let _0x101977=0xa32+0xf39*-0x2+0x1441*0x1;_0x101977<=_0x2ef4df;_0x101977++){for(let _0xd8eec=-0x26*-0x2+0x4*0x11b+-0x4b7;_0xd8eec<=_0x625691;_0xd8eec++){_0x2ad9f5[_0x101977-(-0x419*0x1+-0x1c0a+0x2ec*0xb)]===_0x5e38dc[_0xd8eec-(-0x357+0x2*0x8f7+-0xe96)]?_0x357a2a[_0x101977][_0xd8eec]=_0x357a2a[_0x101977-(-0x665+-0x1097+-0x217*-0xb)][_0xd8eec-(0x3f1+0xb3+-0x4a3)]:_0x357a2a[_0x101977][_0xd8eec]=Math[_0x1179fc(0x266,'K[y@')](_0x357a2a[_0x101977-(0x110d+-0x1a9c+0x3*0x330)][_0xd8eec],_0x357a2a[_0x101977][_0xd8eec-(0x9f3+0xfbd+-0x5*0x523)],_0x357a2a[_0x101977-(-0xf3c+-0x11c4*-0x1+0x287*-0x1)][_0xd8eec-(0x1a5e+-0x1*0xa16+-0x1047)])+(-0x1fca*-0x1+0x1d*-0xb7+-0xb0e);}}return _0x357a2a[_0x2ef4df][_0x625691];}[a0_0x3a828b(0x218,'RZxv')](_0x52e1a2,_0x355d8a){const _0x108f2f=a0_0x3a828b;if(_0x52e1a2[_0x108f2f(0x247,'CPOi')]('id')&&_0x355d8a[_0x108f2f(0x200,'^O[s')]>-0xba4+0x2*-0x899+0x1cd8)return _0x108f2f(0x265,'T#!%');if(_0x355d8a[_0x108f2f(0x258,'bLVY')]>=-0x55d+-0x1*-0x1f67+-0x1a07)return _0x108f2f(0x229,'#OJc');return _0x108f2f(0x23d,'Ao0A');}[a0_0x3a828b(0x230,'VzuO')](_0x62ff1f,_0x17d469,_0x366896){const _0x5c7f63=a0_0x3a828b;if(_0x366896)return _0x5c7f63(0x1ff,'3enR');const _0x2ec8c3=new Map();for(const _0x5bcf43 of _0x17d469){const _0x4b1e3a=_0x5bcf43[_0x5c7f63(0x22f,'z!y@')]('.')[0x2*0x752+0x4a5*0x2+-0x31*0x7d]||_0x62ff1f;_0x2ec8c3[_0x5c7f63(0x249,'*TYt')](_0x4b1e3a,(_0x2ec8c3[_0x5c7f63(0x23c,'V7%@')](_0x4b1e3a)||0xdbd+0x1666+-0x1d*0x13f)+(-0x2f*-0xce+-0x21f8+-0x5*0xc5));}const _0x6d7bc9=Array[_0x5c7f63(0x217,'ctJ&')](_0x2ec8c3[_0x5c7f63(0x264,'NTUd')]())[_0x5c7f63(0x241,'ib$4')]((_0xd36c08,_0x5afe50)=>_0x5afe50[-0xa00+0xa6*-0x1+0xaa7]-_0xd36c08[-0x1c81+0x10db*-0x1+-0x4f*-0x93])[-0x7ed+0x1*-0xc83+0xda*0x18][0x75c+-0x259+-0x1*0x503];return _0x5c7f63(0x25d,'CY)0')+_0x6d7bc9+_0x5c7f63(0x233,'z!y@')+_0x17d469[_0x5c7f63(0x228,'C!9P')](',\x20');}[a0_0x3a828b(0x25c,'mUk8')](_0x8c1a4b,_0x29ed96){const _0x45e3e5=a0_0x3a828b,_0x5b2e4e=_0x8c1a4b[_0x45e3e5(0x26b,'CPOi')],_0x143d2a=_0x29ed96[_0x45e3e5(0x269,'8(AQ')](',\x20');return _0x45e3e5(0x206,'yk3@')+_0x5b2e4e+_0x45e3e5(0x24c,'A9H8')+_0x143d2a+_0x45e3e5(0x24a,'P]Bj');}}function a0_0x2555(){const _0x1b551a=['W58JW4TrgmolWQrfl8k6','hCoyW7K','WQyvW73dK8kCsq','AWnsdIxdRmorWRy','oSo3W6iuzSkJqHCEw8ok','edpdTq','66IM65kntU2fNoYCIoU4N+YxKEYgG0pSN6pQT7lRKR1u64EO7j+X67ojW73SG7NSMAWs7kw4WP3dIoY0TEQWNSor7kcD7lQ1W6NRTiJTLjFSMj05','WRTwg8kzfCkU','nmkJe8oJW74iWRhdKCkwpSo2','zWJdV8ol','WQ3cPSo7yW','W7RcRINdLq','WOzqu8kY','7yAz7j2c67U9hCoC','WP7cVM/dKCowW4e9WPPAi2vMW7G','W4BdRZhcJa','WOPNWOWwrSkFW6nemmkMW4jPaa','WPPwh8o0w8osWOFdQmoV','WRFcVCo0WQW','omk9WQRdSq','WP8sW6LlWQ9vlKhcQmoQEZmq','WQNcP8o1WQKZwqJdM1xdLSoIWQVdImoKpZ/dRmkV','W6yktmonvCo3WP7cIKDPomovoq','vczQW6NcOSoNdq','qCk4wq','rdtdJSoDFIZcPW','WPnqrCk7','mCobWRjhW6pcOeOcW5mAW71FxwVcHComW7dcJreOoa','W5FdImok','W6iiahCiyW','jCoGwCkc','WQBcU8o0WQeHrIFdIfxdVmoVWORdKCoWhtFdOSkRWRLBW5SKB8oVsqyeW7S4vW','WOTphmo7sG','W49Kk0G','WQnsf8ksbmkiW4BcVM8','WR5PaSkOWO/cOG','ghL/','t2ddImohDdxcMCoD','W7Widx8rzMtdSqFdI8ktW4myWQNdImkHWQ8YWQa','lmoFW6ZdUg9+WPddMG','CCo/zSk1WR1BWQhdOCkwcmovWQO','DGjsWO4','W6Khcb/cQSkKW5mPW6OarLVdJCkTkdrcW6hdV8kiWQm/W5NdQW','W7qkW5DKiX4','yhCwW4dcIh3dIq','ofRcQ8kxDqhdG8kOWQNcImkJBa','W6qLW4RdImkiwq','WR4AWQ4g','W6/dVsOpwSkVzG','AXntWOlcTW','W6mdqmomvmo1WPhcMwvxm8oica','qmk8r8kTW6tcMG','BvLseGJdGSoW','W74hbHdcUSkgW5mWW6O','jSouWQPlW7q','fXfXW6RcLCoWotPpuYvTW4jcWPxcGCo8','hbRcI8kdWORdRaaXWPLnamo9FG','WRLCb8ktamkQW47cQw9wo8oebvjTAmocBG','CSke7zUt7yo+66oCW7tTHRVSNiVcG+Q3UUYCSriC7jIE7zwfWRdcUq','W6GHW40','WOPscCo9xCoOWQldSmo6mSk+WRVcPSo8CgaMWPNdNSkYW5S','AbRdOColmKtdP8kYWQ3cOCkbybneBLFcOW7cK8kXF8kjWR5hW6FcImkpW7FcILy','WRfAgCkkbmk0','W6CSdxVcOty','W7tdUtygr8kP','W4RcSLrBW7/cGmoGWQpdJa7dUfVdIq','hJpdQCouAG/cImolWOHBW6anWRFcMmk2WQpdVmoIW6mCW4xdHmkitq','W4LaWQS','WPhdRqin','xSk8sSkLW73cN8kCecakkSojqYWVbCkl','jSobWRi','WQFcQSoJySohsCoqcCoakx5dW4ldSWj7v8o0lSoqW4O','W5hcV8oqmW','hSkJW4j9','W7lcPmofWOKxFGy','WQhcUSoOWQK','ESkiW5xdRwDCWQtdJrdcOHJcG8kufGPwW5hdOSk5hSoz','W68zgMivAxtdQWBdRa','W7zRzceHWOFdOsS','p8k1WRldUmojzZ8ekW','W4X0la','oEQZRCkb7j6O7ikO7zwb64IO64MCtSoU7kwG67cvW5xRJjlSNjVTHllcKoUAKoUjGvVSNllRQklRK7dcKUYIO+Q3ME2ANUYFRCoR7iQXioYEOUYkVEUjLoUiLsL267oD7zEGe+UyP+UiQCoR6REl6RcCWOdSN7RSHzVQS6hRPyPl6Rko7ysV7zAe7iEF7jMfW6a','W6SgW5rQoXCAymogW6W','WQFQSRZcNSkk','WPhdQbFcG8kaWR1h','lCo5W4iyzmkQusaAFmooxbpcVdv9WOO0qI8EmmkHWP0','Dqjp','b31/W6NdRmk5WO4','W47cTComihTk','dH/cGCkZ','W75XCZ89WOhdSsZdPSkU','uCkSs8khnCk3ha','WRHDxsHlodxdNHVdKCkkW5uh','zKGmW7NcHg3dLW','g3LIW6a','W6xcOJ3dL8ouqa','WPtdPGOfWRRdNW','BCkDW43dOxa','f8o5dSozyCoNt3KwxNLQzW','WPXshSo3u8oXWOpdQ8oUcSk2WRRcQ8oTwem9WPVdLW','64AZ7j6Q67gJW5hRTyZSN7pSUP7dRo2aSEYLPxhcVSk+','WOjse8o/sSo0','WPLCr8kWE24','WQyBW6e','nLPImIZdPCo+WOKLWPNcMmkiWOa8WQbuWRdcGG','D8kIbCoAwSoWqJdcSmoWW6pdSZe','W5SiWORcOSowiCo2mmoRWQRdRu3cOSkxCCo4W57dKmkvWQ/cLW','ehz7W7FdPmk/WPG','W50LW5zxgmokWRbD','W6CpcG','EGddV8ohlvBdKW','tKdcUCkhW5K5zZq','fXNcKSk4','W5/cHuFdOCoCW6G','W6TKzseXWQ3dPtxdPG','Dmo/vmk6o8kSpW','W43cJuNdUCoCW5rQW5jt','W5ZdImoipSk/CqDlwXfnbHOVDY3dPqVcMG','WQ7cOCoHDCoax8ol','jCorWRvk','WR8BW7VdMSkvDdWxW6G','W6T8DYG','rmk4wG','W7ZdUsWeumk1rxVcNSkHgILkWRmeeSkwW5WoWO/cNq','W7GZW7DElSkYycZdPw4vnIO','umkGvSkcomk1cW','W4jaWQKyW7KFDNdcI8oLvWyXW5fdEIu7W4C','WRHxWPe','W64VW4ZdGmkkqxBdOCoZWRtdUGvjWQJdOmkAmge'];a0_0x2555=function(){return _0x1b551a;};return a0_0x2555();}
|
|
1
|
+
const a0_0x2552e6=a0_0x1d91;(function(_0x1798d0,_0x3ab988){const _0x435245=a0_0x1d91,_0x52fcb3=_0x1798d0();while(!![]){try{const _0x274d6d=parseInt(_0x435245(0x1cf,'Cdxh'))/(0x1f67+-0xf8b*0x1+-0xfdb)+parseInt(_0x435245(0x17e,'Cdxh'))/(-0x1*0x22e3+-0x1b89+-0x7a*-0x83)+parseInt(_0x435245(0x1a9,'jUV['))/(-0x3*0x2ab+0x17aa+-0xfa6*0x1)+-parseInt(_0x435245(0x191,'gdH8'))/(-0x1b7c+0x1efa+-0x37a)*(parseInt(_0x435245(0x1b4,'Ecdy'))/(0x50*-0x48+0x659*0x6+-0xf91*0x1))+-parseInt(_0x435245(0x1dd,'JMXf'))/(0x6*-0x511+0x1520+-0x11*-0x8c)*(-parseInt(_0x435245(0x187,'SUDM'))/(-0x5*-0x11b+0x1*0x17fd+0x1*-0x1d7d))+-parseInt(_0x435245(0x190,'$);}catch(_0x4ff5c1){_0x52fcb3['push'](_0x52fcb3['shift']());}}}(a0_0x2137,-0x3f74*0x1a+-0x155e4*0x1+0xb6d91));export class DuplicateDetector{[a0_0x2552e6(0x1ce,')v^Z')](_0x4bf567){const _0x1e771d=a0_0x2552e6,_0x27259a=this[_0x1e771d(0x19b,'!([K')](_0x4bf567[_0x1e771d(0x169,'5FYr')]),_0x1d358a=this[_0x1e771d(0x1ac,'$]eZ')](_0x4bf567[_0x1e771d(0x1c6,'jUV[')]),_0xf121c=_0x27259a[_0x1e771d(0x1a2,'2yax')](_0x76ee27=>_0x76ee27[_0x1e771d(0x178,'3Gtk')]===_0x1e771d(0x1ae,'N5$h'))[_0x1e771d(0x1df,'Atdi')]+_0x1d358a[_0x1e771d(0x1b9,'d8Cz')](_0x5dc264=>_0x5dc264[_0x1e771d(0x1a3,'5FYr')]>-0x2707+0x445+0x22c2+0.8)[_0x1e771d(0x16e,'l!^H')];return{'fields':_0x27259a,'tables':_0x1d358a,'summary':{'totalDuplicateFields':_0x27259a[_0x1e771d(0x196,'n!ed')],'totalDuplicateTables':_0x1d358a[_0x1e771d(0x17a,'cvCb')],'autoFixable':_0xf121c}};}[a0_0x2552e6(0x1ab,'Ecdy')](_0x354b8c){const _0x21b744=a0_0x2552e6,_0x214c21=[],_0x11c2b7=new Map();for(const _0x45c2bd of _0x354b8c){for(const _0x5a65c5 of _0x45c2bd[_0x21b744(0x184,'Ecdy')]){const _0x27a6e0=this[_0x21b744(0x1d6,'mdA4')](_0x5a65c5[_0x21b744(0x17d,'Atdi')]);!_0x11c2b7[_0x21b744(0x1dc,'mUWZ')](_0x27a6e0)&&_0x11c2b7[_0x21b744(0x1ba,'jUV[')](_0x27a6e0,[]),_0x11c2b7[_0x21b744(0x1d7,'D^E%')](_0x27a6e0)[_0x21b744(0x1c5,'f7Pm')](_0x45c2bd[_0x21b744(0x1d9,'D^E%')]+'.'+_0x5a65c5[_0x21b744(0x185,'$]eZ')]);}}for(const [_0x531225,_0x62c89d]of _0x11c2b7[_0x21b744(0x174,'i24c')]()){if(_0x62c89d[_0x21b744(0x1e1,'i24c')]<=-0x102d*0x2+-0x9be+-0x2a19*-0x1)continue;const _0xba6d67=new Set(_0x62c89d[_0x21b744(0x1b6,'!([K')](_0x2abbc4=>_0x2abbc4[_0x21b744(0x1cd,'Atdi')]('.')[0x1c09*0x1+0x21da+-0x3de2])),_0x54165d=_0xba6d67[_0x21b744(0x168,'HdoI')]===0xd01*-0x3+0xb*0x344+0x318,_0x3a1b8a=_0x62c89d[_0x21b744(0x1cc,'f7Pm')](_0xa5eab7=>_0xa5eab7[_0x21b744(0x1e0,'Ecdy')]('.')[0x6*-0x5b0+-0x1fbd*-0x1+0x2f*0xd]);_0x214c21[_0x21b744(0x176,'@y(T')]({'fieldName':_0x531225,'tables':_0x3a1b8a,'type':_0x54165d?_0x21b744(0x1cb,'5FYr'):_0x21b744(0x19d,'$]eZ'),'severity':this[_0x21b744(0x1c2,'l!^H')](_0x531225,_0x3a1b8a),'recommendation':this[_0x21b744(0x179,'iv@3')](_0x531225,_0x3a1b8a,_0x54165d)});}return _0x214c21;}[a0_0x2552e6(0x186,'l!^H')](_0x35bab6){const _0x55b6b1=a0_0x2552e6,_0x146f78=[];for(let _0x2a83e8=0x1af6+0x17*0x99+-0x28b5;_0x2a83e8<_0x35bab6[_0x55b6b1(0x1c7,'qGJJ')];_0x2a83e8++){const _0x14bfd9=_0x35bab6[_0x2a83e8],_0x372a3b=[];for(let _0x16df52=_0x2a83e8+(0x2f5*0xd+0xeab*0x1+-0x351b);_0x16df52<_0x35bab6[_0x55b6b1(0x1af,'y]4c')];_0x16df52++){const _0x21d36d=_0x35bab6[_0x16df52],_0x31ce82=this[_0x55b6b1(0x1a7,'1wav')](_0x14bfd9,_0x21d36d);_0x31ce82>-0x1694+0x2b*-0x3d+0x3*0xaf1+0.6&&_0x372a3b[_0x55b6b1(0x1a5,'i24c')](_0x21d36d[_0x55b6b1(0x19e,'SUDM')]);}_0x372a3b[_0x55b6b1(0x197,'a@9U')]>-0x2025+-0x2327+0x434c&&_0x146f78[_0x55b6b1(0x194,'D^E%')]({'tableName':_0x14bfd9[_0x55b6b1(0x16a,'rEQ2')],'similarTo':_0x372a3b,'similarity':Math[_0x55b6b1(0x198,'d8Cz')](..._0x372a3b[_0x55b6b1(0x1da,'Cdxh')](_0x127b34=>{const _0x4d9f79=_0x55b6b1,_0x239bd5=_0x35bab6[_0x4d9f79(0x1a6,'2yax')](_0x3d0278=>_0x3d0278[_0x4d9f79(0x1a4,'BX]]')]===_0x127b34);return this[_0x4d9f79(0x1c0,'2yax')](_0x14bfd9,_0x239bd5);})),'recommendation':this[_0x55b6b1(0x17f,'l!^H')](_0x14bfd9,_0x372a3b)});}return _0x146f78;}[a0_0x2552e6(0x1b8,'SUDM')](_0x34734e){const _0x50ed16=a0_0x2552e6;return _0x34734e[_0x50ed16(0x199,'Ecdy')](/[-_]/g,'')[_0x50ed16(0x192,'f7Pm')](/([A-Z])/g,_0x2f5978=>_0x2f5978[_0x50ed16(0x1d3,'U(Nh')]())[_0x50ed16(0x175,'y]4c')]();}[a0_0x2552e6(0x18c,'qGJJ')](_0x3ee852,_0x4f0ebc){const _0x3c3e4d=a0_0x2552e6,_0x1de0e6=this[_0x3c3e4d(0x1a0,'sYzC')](_0x3ee852[_0x3c3e4d(0x16b,'sYzC')],_0x4f0ebc[_0x3c3e4d(0x1d8,'#XJl')]),_0x30345f=new Set(_0x3ee852[_0x3c3e4d(0x1c8,'rEQ2')][_0x3c3e4d(0x189,'8HlF')](_0x1b0954=>this[_0x3c3e4d(0x170,'$]eZ')](_0x1b0954[_0x3c3e4d(0x19a,'ER$c')]))),_0x180330=new Set(_0x4f0ebc[_0x3c3e4d(0x1ad,'l!^H')][_0x3c3e4d(0x1db,'d8Cz')](_0xe953f=>this[_0x3c3e4d(0x1bf,'#XJl')](_0xe953f[_0x3c3e4d(0x1b0,'L1gt')]))),_0x25b076=new Set([..._0x30345f][_0x3c3e4d(0x1a2,'2yax')](_0x55c02f=>_0x180330[_0x3c3e4d(0x1c1,'mdA4')](_0x55c02f))),_0xcf171c=new Set([..._0x30345f,..._0x180330]),_0x22bf96=_0xcf171c[_0x3c3e4d(0x1a8,'3iCZ')]>0x1*-0xf0d+0xab2+-0x5*-0xdf?_0x25b076[_0x3c3e4d(0x17b,'sYzC')]/_0xcf171c[_0x3c3e4d(0x1e3,'ER$c')]:0x1161+0xac0+-0x1c21;return _0x1de0e6*(0x3*-0x1ee+0x26fa+-0x2130+0.3)+_0x22bf96*(0x913*-0x2+0x143c+-0x216+0.7);}[a0_0x2552e6(0x1d1,'SUDM')](_0x5b0767,_0x9ea6e){const _0x4cc904=a0_0x2552e6,_0x648a76=this[_0x4cc904(0x18a,'BX]]')](_0x5b0767,_0x9ea6e),_0x26bce3=Math[_0x4cc904(0x1de,'jUV[')](_0x5b0767[_0x4cc904(0x18b,'8HlF')],_0x9ea6e[_0x4cc904(0x16d,'jUV[')]);return _0x26bce3>0xac7*-0x1+0x114e+0x1*-0x687?-0x1776+0x7*0x575+-0xebc-_0x648a76/_0x26bce3:0x1*-0x162b+-0x57*0x5+-0x123*-0x15;}[a0_0x2552e6(0x18a,'BX]]')](_0x1487d2,_0x69040b){const _0xd0c348=a0_0x2552e6,_0x3503dc=_0x1487d2[_0xd0c348(0x182,'[gKm')],_0x1fb1a4=_0x69040b[_0xd0c348(0x1ca,'3iCZ')],_0x353752=Array[_0xd0c348(0x1d2,'$]eZ')]({'length':_0x3503dc+(0x35*0x59+0x1394+-0x2600)},()=>Array(_0x1fb1a4+(0x60a+0x1*-0x89f+-0x296*-0x1))[_0xd0c348(0x1c9,'$]eZ')](-0x1c62+0x4*0x346+0xf4a));for(let _0x125173=0x1*0x23a1+0x2f*-0xa+0x1*-0x21cb;_0x125173<=_0x3503dc;_0x125173++)_0x353752[_0x125173][0x1*0x16d+-0x656*-0x5+-0x211b]=_0x125173;for(let _0x3c1d40=0x1e93+-0x2c8*0x6+0xf*-0xed;_0x3c1d40<=_0x1fb1a4;_0x3c1d40++)_0x353752[-0x7*0x3cd+-0x2121+-0x3bbc*-0x1][_0x3c1d40]=_0x3c1d40;for(let _0x2ffd98=-0x4a1+-0x270f+0x8bd*0x5;_0x2ffd98<=_0x3503dc;_0x2ffd98++){for(let _0x161a71=0xc86+0x7bc+0x11*-0x131;_0x161a71<=_0x1fb1a4;_0x161a71++){_0x1487d2[_0x2ffd98-(0x1*0x24a3+0x26e6+-0x4b88)]===_0x69040b[_0x161a71-(-0x15c9+-0x949*-0x1+0xc81)]?_0x353752[_0x2ffd98][_0x161a71]=_0x353752[_0x2ffd98-(-0x62a+0xa31*-0x1+0x105c)][_0x161a71-(-0x782+-0x1c4*0xb+0x7*0x3d9)]:_0x353752[_0x2ffd98][_0x161a71]=Math[_0xd0c348(0x1d0,'o9ey')](_0x353752[_0x2ffd98-(0x23+0x2110+-0x2132)][_0x161a71],_0x353752[_0x2ffd98][_0x161a71-(0xc1*0x5+-0x16*0xb5+0xbca)],_0x353752[_0x2ffd98-(0xa1e+-0x185*-0xb+-0x1ad4)][_0x161a71-(-0x1*0x12fa+-0x5b*0x5d+0x340a)])+(-0x1d25+0x13a*0x1+0x1bec);}}return _0x353752[_0x3503dc][_0x1fb1a4];}[a0_0x2552e6(0x188,'gUf&')](_0x5879df,_0x17d8c6){const _0x151921=a0_0x2552e6;if(_0x5879df[_0x151921(0x19c,'Cdxh')]('id')&&_0x17d8c6[_0x151921(0x1b3,'3Gtk')]>-0xa25+-0x1*-0x1508+0x22d*-0x5)return _0x151921(0x1e2,'BX]]');if(_0x17d8c6[_0x151921(0x16d,'jUV[')]>=-0x1f31*-0x1+0x11*-0x248+0x2*0x3cd)return _0x151921(0x1be,'mdA4');return _0x151921(0x1b5,'Atdi');}[a0_0x2552e6(0x181,'rEQ2')](_0x1dc12b,_0x461354,_0x432bd1){const _0x348281=a0_0x2552e6;if(_0x432bd1)return _0x348281(0x177,'y]4c');const _0x2c8763=new Map();for(const _0x5e7e3d of _0x461354){const _0x4063a7=_0x5e7e3d[_0x348281(0x172,'1wav')]('.')[-0xc58+-0xa27*-0x2+0x123*-0x7]||_0x1dc12b;_0x2c8763[_0x348281(0x171,'$]eZ')](_0x4063a7,(_0x2c8763[_0x348281(0x1bd,'$]eZ')](_0x4063a7)||-0x4a9*-0x1+-0x2280+0x1dd7)+(0xb*-0x15a+-0x1c2c+0xe59*0x3));}const _0x55ef19=Array[_0x348281(0x1d2,'$]eZ')](_0x2c8763[_0x348281(0x1d4,'BX]]')]())[_0x348281(0x183,'mdA4')]((_0x984293,_0x5a87b7)=>_0x5a87b7[-0x10db*0x1+0x5ea*-0x1+0x16c6]-_0x984293[-0xb82+0x4*0x90f+0x18b9*-0x1])[0x24da+0x1f36+-0x4410*0x1][-0x502*-0x3+0xd57*0x2+-0x29b4];return _0x348281(0x18f,'D^E%')+_0x55ef19+_0x348281(0x173,'Hf4Q')+_0x461354[_0x348281(0x1a1,'N5$h')](',\x20');}[a0_0x2552e6(0x1d5,'i24c')](_0x2e3169,_0x12757e){const _0x2a9182=a0_0x2552e6,_0x204693=_0x2e3169[_0x2a9182(0x17c,'o9ey')],_0x19a01d=_0x12757e[_0x2a9182(0x1c4,')v^Z')](',\x20');return _0x2a9182(0x1b2,'$![$')+_0x204693+_0x2a9182(0x1b7,'[gKm')+_0x19a01d+_0x2a9182(0x19f,'d8Cz');}}function a0_0x1d91(_0x5c679e,_0x5f2a71){_0x5c679e=_0x5c679e-(0x104b+-0xc97+-0x24c);const _0x36c2e1=a0_0x2137();let _0x31d2de=_0x36c2e1[_0x5c679e];if(a0_0x1d91['wftNWs']===undefined){var _0xdd5aaa=function(_0x54c4c9){const _0x118db0='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x2a434d='',_0x282a56='';for(let _0x233fa8=-0x181b+0x18b+-0x1690*-0x1,_0xfac57a,_0x31519b,_0x50e039=0x445+-0x26d+-0x1d8;_0x31519b=_0x54c4c9['charAt'](_0x50e039++);~_0x31519b&&(_0xfac57a=_0x233fa8%(-0x9be+0x46+-0x4be*-0x2)?_0xfac57a*(0x1c09*0x1+0x21da+-0x3da3)+_0x31519b:_0x31519b,_0x233fa8++%(0xd01*-0x3+0xb*0x344+0x31b))?_0x2a434d+=String['fromCharCode'](0x6*-0x5b0+-0x1fbd*-0x1+0x1b1*0x2&_0xfac57a>>(-(0x1af6+0x17*0x99+-0x28b3)*_0x233fa8&0x2f5*0xd+0xeab*0x1+-0x3516)):-0x1694+0x2b*-0x3d+0x3*0xaf1){_0x31519b=_0x118db0['indexOf'](_0x31519b);}for(let _0x29cc8c=-0x2025+-0x2327+0x434c,_0x2686a0=_0x2a434d['length'];_0x29cc8c<_0x2686a0;_0x29cc8c++){_0x282a56+='%'+('00'+_0x2a434d['charCodeAt'](_0x29cc8c)['toString'](0x1*-0xf0d+0xab2+-0x1d*-0x27))['slice'](-(0x1161+0xac0+-0x1c1f));}return decodeURIComponent(_0x282a56);};const _0x4638f3=function(_0x17fb47,_0x1231cd){let _0x3b14cc=[],_0xf1206e=0x3*-0x1ee+0x26fa+-0x2130,_0x381807,_0x40214e='';_0x17fb47=_0xdd5aaa(_0x17fb47);let _0x2fdab9;for(_0x2fdab9=0x913*-0x2+0x143c+-0x216;_0x2fdab9<0xac7*-0x1+0x114e+0x1*-0x587;_0x2fdab9++){_0x3b14cc[_0x2fdab9]=_0x2fdab9;}for(_0x2fdab9=-0x1776+0x7*0x575+-0xebd;_0x2fdab9<0x1*-0x162b+-0x57*0x5+-0x425*-0x6;_0x2fdab9++){_0xf1206e=(_0xf1206e+_0x3b14cc[_0x2fdab9]+_0x1231cd['charCodeAt'](_0x2fdab9%_0x1231cd['length']))%(0x35*0x59+0x1394+-0x2501),_0x381807=_0x3b14cc[_0x2fdab9],_0x3b14cc[_0x2fdab9]=_0x3b14cc[_0xf1206e],_0x3b14cc[_0xf1206e]=_0x381807;}_0x2fdab9=0x60a+0x1*-0x89f+-0x295*-0x1,_0xf1206e=-0x1c62+0x4*0x346+0xf4a;for(let _0x580a7b=0x1*0x23a1+0x2f*-0xa+0x1*-0x21cb;_0x580a7b<_0x17fb47['length'];_0x580a7b++){_0x2fdab9=(_0x2fdab9+(0x1*0x16d+-0x656*-0x5+-0x211a))%(0x1e93+-0x2c8*0x6+0x1*-0xce3),_0xf1206e=(_0xf1206e+_0x3b14cc[_0x2fdab9])%(-0x7*0x3cd+-0x2121+-0xf2f*-0x4),_0x381807=_0x3b14cc[_0x2fdab9],_0x3b14cc[_0x2fdab9]=_0x3b14cc[_0xf1206e],_0x3b14cc[_0xf1206e]=_0x381807,_0x40214e+=String['fromCharCode'](_0x17fb47['charCodeAt'](_0x580a7b)^_0x3b14cc[(_0x3b14cc[_0x2fdab9]+_0x3b14cc[_0xf1206e])%(-0x4a1+-0x270f+0x1b8*0x1a)]);}return _0x40214e;};a0_0x1d91['MDhMDf']=_0x4638f3,a0_0x1d91['pAYNKa']={},a0_0x1d91['wftNWs']=!![];}const _0x27a77d=_0x36c2e1[0xc86+0x7bc+0x2*-0xa21],_0x15195b=_0x5c679e+_0x27a77d,_0x3b0572=a0_0x1d91['pAYNKa'][_0x15195b];return!_0x3b0572?(a0_0x1d91['AuYEDl']===undefined&&(a0_0x1d91['AuYEDl']=!![]),_0x31d2de=a0_0x1d91['MDhMDf'](_0x31d2de,_0x5f2a71),a0_0x1d91['pAYNKa'][_0x15195b]=_0x31d2de):_0x31d2de=_0x3b0572,_0x31d2de;}function a0_0x2137(){const _0x382874=['WPbdWRvzWRNdTmo2bmkO','W7ddVgJcNSkwFcSWWQu','W7r6WQ8','j8kEcq','rYxcGW','yCoCW6dcNI/cSIWojSo6WRDm','WPL0W7m','W6niW6qdW5aj','W7lcG8kZW6OA','DYJdImkwW5ZcMa','W4BcJSoTWQtcK8k/WOhcVq','WQu7nSkL','icXvWR8','fCkwW6mGW4lcKa','WQZcHSoBW7ldLMfsW7ZdUa','WQ3dMmo/yIBcUe3dHCkC','W59+aL/cSHuAWOhdRa3cPxC','WPHWW6usbW8','WONdMhhcS8o+rG','ka/dVfBcSSk2ySoXW5VdSCkeW78','DbtdLSoKy0JcJ04ndCkWzGZcQSkCddBcLq','Ar7dKa','qmoLWQzBtW','WPtdRU2yRE2dREUJI8kL7yAa7j+EW6ZQTlNSNPuQWOhSMAFTLi3cKmkS','FIpdKSkdW4hcLCo5','W5BcHZhcTdyfuf3dQ8oAtq','WOCKWO8d','66Ik65cixE2gL+YCTEU5ToYxSUYeGSkQ7j+v6RsO65crcEUfNEYENEUZMmkv7ig57jU0W5JSP7BdUSoq7lEc6RgXEUYJT+Y7ICkA67Es7zA57jIRW6G','WRGgWPZcGW','W6FdGsJcSCo+W75RW57cUWmVeCo9W4P2W4fo','CvJcQWtdSCoS','WQRdKmoNAW','W4tcVq1CW7NdKSkUWRpcJW','W6fmW6Cb','WQSIW67dVMRcSdj1o8kytaW','WPFdMhZcU8oNq8ocg8kVWPnvcCkrlCk2sGNcOr0','W6DJFmo2emoBW7S/W6qkWRPN','WQRcGSoAW7hdNKjwW7/dUr5WWOJcM8k7WP5UFW','a2m+WPPLW54','zfOxwG','W6dcH8kRW7ehcJFdK2aG','DbRdICoS','WOhdMgVcSCoPwSoJamk7WQTDcmkCpmkEEXRcPbqaqW','W4PPW44fddfaia','xdlcT8klWPpdR1azsLpcQafOWPn5vhBdMmkkWR8CWO4pWP3cU8o7eLC9WQK','WOaMqW','W4NcMCoYWRxcLmkVWOJcPCoNWRqYWRtdRCk/W4ddNmkJW5BcOa','WOeIxq3dV0u','eCoqW5iTkCo8gSk4WRO5eSknWO/dQfhcKuuVp8kuW77dVSk7W6S','k0RcLmk/nXhcI3K8mSkJwq','k0RcLmolzxFcRL4D','64AG7j+P67chW5lRTBVSNy7SUjj97ycq7ksSySkcW6i','WOH9zCopcwXbWQH8W6ZdHSkR','wmkpW5xcVubupmksW5G','W5zWzt4DW7Sx','W7TEqCkTW6nTi8kzjSo6WOVcPW','W7tdQhNcMG','zNexW6VdJXhcJmolWOWqk8o6','wMH8WOrtka','qr3dK3hcGgC','j8kEaq','W7pcLSkVW68pcYC','WRGZiCkL','W63cR2OtWQ7dGmkawSoWWQ3cR8oPymkMkGBcHmkPxmo4WPS','W7b1WRZcPYBdRbLQ','ArldICoGBKxcLa','WOW5W7KnpHTJpmkK','BEQZV1NSNj7SGitTLyRRI67RIPu5WOhSP5JRSluY64257j2K7ywWW6hRMlVRI6W57j2g66U765k0WRtSOy/QTztTMkdSNPddOUYlHCkL7jY87iMc64Q064UHlapRSRhTL7BdKUUyMUUlTSkI6RwX6RkCW5RSNQFSHQlQS5hRPB/dQ+QWN+2hV+2vOEYfRUYzJv4','WRxdNmoRAY3cHutdNmkCiv0wW53dMaxdTSoFpSkCBdm','W6y7eSot','WPP2W7FcSfFcHq','c8kqW6OSW4lcGSoqW4OEoG','W5hcNCoMWRZcN8ksWOhcVmoN','AZJdLCkz','WPP2W7xcOa','umo0WQzrtSoPr8opW5TFBhJdR3ecjHJcJCocjmkddG5H','w3G9ra','W4eNWRjarfxcQwPMW4fOW5i','qmkSWO5Mymk5z8kwWO55Bq','W6xcLSkRW6ynhaBdKNu/WPJcPXOYp8o7kJBcGL/cNG','FH7dKmoSyvdcOKeyj8kWyahcUSk3otRcKSk+WPKi','WOtdIwVcPSoJtmosaCkUWRq','W789fSouWR9tWOG','W47cJrpcVdui','W6C3W7Td','r8kVWOS9pSkNEmk0WQO','7yEX7jY767McW5yz','WQaAWOlcGwjF','WRFdGmoQWRbBlYJdThyjWR8','W6zdW6Wl','W6tcQ24','soQZUNddMG','WPy3W6KmoJLRk8kKgvBcUdddQCoZW6XpWRC','lmkwfuRdJve','WODWW78','W6DJFmkcqSk9W54yW4u','WRRdTrWLWR4vASk7','Fr7dKa','yfqxqmoLW7Dq','WOPnWQvyWR3dLSo+e8kOWQVdGsbZgZJdSSouW5C','WP9+W7FcP0FcMXddQhfjAe0XWRpdLCosW4ZcM8otnCo4t3S1','F1qw','WOtdJMZcSCo5xCoHhmkUWQTql8kiomkxrHJcPWWmx8ofWRXrzSogWO8rWRVdGG','WOJdMmkzktpcUSkJWO3cLfyWW6y','hmoWW5i7','W5rGzJO','WPL6W68qhXq','hSouW5aPkmo4','WRNcK8onW6ZdMK1gW6xdUcS','FbldImoL','rhqPrSobW68','hCkbW6yMW5O','W4L0zq','W7XDW6ynW5a','eSo6W48WnSkk','WQGTW67dU2tcVH1scmk5AqW','W53cTqe','WPq9W60enszQjCkKnLhcJJxdOmouW6fdWQa+WRD6','FaNdI8oK','WQZcO1Tvd8kFBcNcHtRcSG','W4dcKSoWWQlcK8k5WPm','AsJdHCkEW4xcNCoVW4tcICoCWRpcMCkaaYBcOSo/jCkT','EvOxq8oTW7vEWPKqWQuEWQddI2qaWQ3dLmkV','W6pdUh4'];a0_0x2137=function(){return _0x382874;};return a0_0x2137();}
|