@allansantos-dev/smart-tool 0.0.0-stage → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/LICENSE +202 -0
- package/NOTICE +32 -0
- package/README.md +116 -3
- package/atomic_io.py +94 -0
- package/autostart.py +199 -0
- package/bin/smart-tool.mjs +82 -0
- package/browser_service_client.py +232 -0
- package/capabilities.py +63 -0
- package/client_hooks.py +192 -0
- package/code_graph.py +403 -0
- package/code_graph_java.mjs +49 -0
- package/code_graph_js.cjs +135 -0
- package/config.py +241 -0
- package/daemon_launcher.py +165 -0
- package/directory_watch.py +131 -0
- package/document_text.py +69 -0
- package/duplicates.py +313 -0
- package/embedding_cache.py +99 -0
- package/endpoint_sync.py +179 -0
- package/folder_picker.py +73 -0
- package/freshness.py +278 -0
- package/gateway.py +220 -0
- package/hook_decision.py +96 -0
- package/hooks/pretool_router.py +45 -0
- package/index_inventory.py +75 -0
- package/index_profile.py +167 -0
- package/index_scope.py +458 -0
- package/index_views.py +121 -0
- package/indexer.py +878 -0
- package/install.py +251 -0
- package/install_runtime.py +249 -0
- package/integration_metrics.py +82 -0
- package/local_embedder.py +172 -0
- package/model_adapters/__init__.py +0 -0
- package/model_adapters/openai_compatible.py +57 -0
- package/model_catalog.py +74 -0
- package/model_client.py +38 -0
- package/model_defaults.py +70 -0
- package/package.json +35 -6
- package/paths.py +6 -0
- package/project_identity.py +42 -0
- package/project_insights_ui.py +38 -0
- package/project_maps_ui.py +96 -0
- package/project_monitor.py +204 -0
- package/project_scheduler.py +61 -0
- package/project_storage.py +165 -0
- package/project_store.py +220 -0
- package/projects_ui.py +146 -0
- package/requirements.txt +8 -0
- package/research_cache.py +171 -0
- package/research_metrics.py +110 -0
- package/router.py +582 -0
- package/secret_store.py +121 -0
- package/setup_ui.py +986 -0
- package/site_memory.py +53 -0
- package/smart_tool_daemon.py +3604 -0
- package/tray.py +245 -0
- package/update_check.py +22 -0
- package/usage_meter.py +124 -0
- package/version.py +18 -0
- package/view_compare.py +68 -0
- package/web_adapters/browser/browser_service.py +302 -0
- package/web_adapters/browser/crawl4ai_search.py +236 -0
- package/web_adapters/browser/requirements.txt +2 -0
- package/web_adapters/node/adapter-common.mjs +40 -0
- package/web_adapters/node/ddg-search.mjs +89 -0
- package/web_adapters/node/open-websearch-adapter.mjs +77 -0
- package/web_adapters/node/package-lock.json +2720 -0
- package/web_adapters/node/package.json +12 -0
- package/web_adapters/node/wikipedia-search.mjs +44 -0
- package/web_document_graph.py +71 -0
- package/web_fetch.py +242 -0
- package/web_search_adapters.py +193 -0
- package/web_search_ddgs.py +42 -0
- package/web_search_external.py +288 -0
- package/web_search_health.py +88 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Trilha de calibração da memória (cosseno + rerank) e do teto de pesquisa:
|
|
3
|
+
`site_memory_similarity_threshold` pré-filtra tópicos, o reranker confirma o
|
|
4
|
+
reaproveitamento, e `research_max_iterations` limita o retorno por rodada. Write-only, como
|
|
5
|
+
`router-metrics.jsonl` — nada no daemon lê isto de volta; quem lê é
|
|
6
|
+
`.token-guard/analyze_research.py`.
|
|
7
|
+
|
|
8
|
+
Grava só o que a calibração usa: textos de consulta/tópico truncados e números. Nunca
|
|
9
|
+
snippets nem páginas, que são conteúdo de terceiros e não ajudam a decidir o valor.
|
|
10
|
+
"""
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import paths
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
import atomic_io
|
|
18
|
+
|
|
19
|
+
METRICS_PATH = os.path.join(paths.DATA_DIR, "research-metrics.jsonl")
|
|
20
|
+
METRICS_MAX_BYTES = 2 * 1024 * 1024
|
|
21
|
+
MAX_TEXT_CHARS = 200
|
|
22
|
+
MAX_PAIRS = 10
|
|
23
|
+
|
|
24
|
+
# O daemon é multi-thread (fan-out síncrono e jobs de pesquisa em pools próprios): sem
|
|
25
|
+
# lock, dois appends grandes podem se intercalar na mesma linha.
|
|
26
|
+
_LOCK = threading.Lock()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _short(text):
|
|
30
|
+
text = str(text or "")
|
|
31
|
+
if len(text) > MAX_TEXT_CHARS:
|
|
32
|
+
return text[:MAX_TEXT_CHARS] + "..."
|
|
33
|
+
return text
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _append(record):
|
|
37
|
+
record = {"ts": time.time(), **record}
|
|
38
|
+
try:
|
|
39
|
+
line = json.dumps(record, ensure_ascii=False) + "\n"
|
|
40
|
+
with _LOCK:
|
|
41
|
+
os.makedirs(os.path.dirname(METRICS_PATH), exist_ok=True)
|
|
42
|
+
atomic_io.rotate(METRICS_PATH, METRICS_MAX_BYTES)
|
|
43
|
+
with open(METRICS_PATH, "a", encoding="utf-8") as f:
|
|
44
|
+
f.write(line)
|
|
45
|
+
except (OSError, TypeError, ValueError):
|
|
46
|
+
# Métrica é para calibrar depois: disco cheio ou arquivo travado não pode derrubar
|
|
47
|
+
# uma pesquisa.
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def log_site_memory(query, threshold, scored_topics, recalled, rerank_scores=None, rerank_threshold=None):
|
|
52
|
+
"""Uma correlação da memória do dia. `scored_topics`: `[(tópico, similaridade)]` de
|
|
53
|
+
todos os tópicos comparados — só os `MAX_PAIRS` mais altos vão pro disco, que são os
|
|
54
|
+
que decidem onde o limiar corta."""
|
|
55
|
+
try:
|
|
56
|
+
ranked = sorted(scored_topics, key=lambda pair: pair[1], reverse=True)
|
|
57
|
+
record = {
|
|
58
|
+
"kind": "site_memory",
|
|
59
|
+
"query": _short(query),
|
|
60
|
+
"threshold": threshold,
|
|
61
|
+
"topics": len(ranked),
|
|
62
|
+
"matched": sum(1 for _, score in ranked if score >= threshold),
|
|
63
|
+
"recalled": recalled,
|
|
64
|
+
"pairs": [
|
|
65
|
+
{
|
|
66
|
+
"topic": _short(topic), "score": round(float(score), 4),
|
|
67
|
+
**({"rerank_score": round(float(rerank_scores[topic]), 4)}
|
|
68
|
+
if rerank_scores is not None and topic in rerank_scores else {}),
|
|
69
|
+
}
|
|
70
|
+
for topic, score in ranked[:MAX_PAIRS]
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
if rerank_scores is not None:
|
|
74
|
+
record["rerank_threshold"] = rerank_threshold
|
|
75
|
+
record["rerank_matched"] = sum(
|
|
76
|
+
score >= rerank_threshold for score in rerank_scores.values()
|
|
77
|
+
)
|
|
78
|
+
except Exception:
|
|
79
|
+
return
|
|
80
|
+
_append(record)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def log_research_job(topic, max_iterations, rounds, stopped_by, sources, elapsed_s):
|
|
84
|
+
"""Um job `depth='research'` concluído. `rounds` já traz, por rodada, quanto ela
|
|
85
|
+
acrescentou (`new_urls`, `findings`) — é a curva de retorno marginal que decide o
|
|
86
|
+
teto."""
|
|
87
|
+
try:
|
|
88
|
+
record = {
|
|
89
|
+
"kind": "research",
|
|
90
|
+
"topic": _short(topic),
|
|
91
|
+
"max_iterations": max_iterations,
|
|
92
|
+
"stopped_by": stopped_by,
|
|
93
|
+
"sources": sources,
|
|
94
|
+
"elapsed_s": round(elapsed_s, 1),
|
|
95
|
+
"rounds": [
|
|
96
|
+
{
|
|
97
|
+
"query": _short(r.get("query")),
|
|
98
|
+
"results": r.get("results_count", 0),
|
|
99
|
+
"new_urls": r.get("new_urls", 0),
|
|
100
|
+
"findings": r.get("findings", 0),
|
|
101
|
+
"gaps": r.get("gaps", 0),
|
|
102
|
+
"continue": bool(r.get("continue")),
|
|
103
|
+
"reason": _short(r.get("reason")),
|
|
104
|
+
}
|
|
105
|
+
for r in rounds
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
except Exception:
|
|
109
|
+
return
|
|
110
|
+
_append(record)
|