bookmark-research 0.5.0-beta.1
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/.agents/plugins/marketplace.json +20 -0
- package/.codex-plugin/plugin.json +46 -0
- package/LICENSE +674 -0
- package/README.md +128 -0
- package/README.zh.md +126 -0
- package/bin/bookmark-research.js +30 -0
- package/config/providers.json +98 -0
- package/docs/bookmark-research-architecture.md +124 -0
- package/docs/cli-launcher-plan.md +58 -0
- package/docs/details.en.md +272 -0
- package/docs/details.md +93 -0
- package/docs/harness-compatibility.md +304 -0
- package/docs/harness-sources.json +342 -0
- package/docs/host-validation.json +298 -0
- package/docs/install-design-0.2.0.md +57 -0
- package/docs/installation.en.md +197 -0
- package/docs/installation.md +316 -0
- package/docs/instructions.en.md +42 -0
- package/docs/instructions.md +42 -0
- package/docs/mcp-aggregation-0.2.0.md +92 -0
- package/docs/multi-host-installation-plan.md +163 -0
- package/docs/prompt-reference.en.md +104 -0
- package/docs/prompt-reference.md +104 -0
- package/docs/provider-smoke-0.2.0.json +235 -0
- package/docs/release-notes-0.5.0-beta.1.md +33 -0
- package/docs/research-0.2.0.md +140 -0
- package/docs/research-sources-0.2.0.json +9360 -0
- package/docs/user-guide.en.md +74 -0
- package/docs/user-guide.md +74 -0
- package/docs/validation-0.2.0.md +126 -0
- package/docs/validation-0.4.0.md +154 -0
- package/docs/wiki-quality.md +105 -0
- package/docs/wiki-quality.zh.md +105 -0
- package/hosts/claude/runtime.js +13 -0
- package/hosts/codex/delegate.md +14 -0
- package/hosts/codex/prepare.py +61 -0
- package/hosts/dsh/plugin.js +36 -0
- package/hosts/dsh/runtime.js +14 -0
- package/hosts/dsh/workflow-call.py +45 -0
- package/hosts/pi/register-workflow.py +72 -0
- package/hosts/pi/runtime.js +18 -0
- package/hosts/shared/research-call.py +76 -0
- package/hosts/shared/research-flow.js +208 -0
- package/hosts/shared/workflow.json +15 -0
- package/install.sh +310 -0
- package/package.json +12 -0
- package/scripts/build_zip.py +143 -0
- package/scripts/export_bundle.py +496 -0
- package/scripts/host_assets.py +70 -0
- package/scripts/host_clients.py +196 -0
- package/scripts/host_install.py +277 -0
- package/scripts/install.py +593 -0
- package/scripts/launcher.py +186 -0
- package/scripts/verify_fixture.py +220 -0
- package/scripts/verify_live.py +62 -0
- package/scripts/verify_quality.py +87 -0
- package/skills/bookmark-research/SKILL.md +80 -0
- package/skills/bookmark-research/agents/openai.yaml +3 -0
- package/skills/bookmark-research/references/cli.md +150 -0
- package/skills/bookmark-research/references/deep-research.md +164 -0
- package/skills/bookmark-research/references/github-and-sync.md +30 -0
- package/skills/bookmark-research/references/host-workflows.md +101 -0
- package/skills/bookmark-research/references/package-semantics.md +78 -0
- package/skills/bookmark-research/references/research-methods.md +51 -0
- package/skills/bookmark-research/references/research-services.md +72 -0
- package/skills/bookmark-research/references/research-workflow.md +57 -0
- package/skills/bookmark-research/references/settings-and-archive.md +104 -0
- package/skills/bookmark-research/references/source-lifecycle.md +75 -0
- package/skills/bookmark-research/references/wiki-and-evaluation.md +63 -0
- package/skills/bookmark-research/references/zh/cli.md +150 -0
- package/skills/bookmark-research/references/zh/deep-research.md +164 -0
- package/skills/bookmark-research/references/zh/github-and-sync.md +30 -0
- package/skills/bookmark-research/references/zh/host-workflows.md +101 -0
- package/skills/bookmark-research/references/zh/package-semantics.md +78 -0
- package/skills/bookmark-research/references/zh/research-methods.md +51 -0
- package/skills/bookmark-research/references/zh/research-services.md +70 -0
- package/skills/bookmark-research/references/zh/research-workflow.md +57 -0
- package/skills/bookmark-research/references/zh/settings-and-archive.md +104 -0
- package/skills/bookmark-research/references/zh/skill-guide.md +75 -0
- package/skills/bookmark-research/references/zh/source-lifecycle.md +75 -0
- package/skills/bookmark-research/references/zh/wiki-and-evaluation.md +63 -0
- package/src/archive.py +302 -0
- package/src/bookmark_index.py +913 -0
- package/src/cli.py +435 -0
- package/src/credentials.py +82 -0
- package/src/evaluation.py +337 -0
- package/src/jina.py +96 -0
- package/src/mcp_server.py +641 -0
- package/src/onboarding.py +352 -0
- package/src/provider_adapters.py +152 -0
- package/src/readiness.py +283 -0
- package/src/remote_mcp.py +295 -0
- package/src/research.py +1334 -0
- package/src/research_coverage.py +160 -0
- package/src/research_services.py +684 -0
- package/src/routing.py +202 -0
- package/src/search_results.py +244 -0
- package/src/service_http.py +139 -0
- package/src/settings.py +247 -0
- package/src/source_inputs.py +201 -0
- package/src/source_manager.py +348 -0
- package/src/source_watcher.py +68 -0
- package/src/textio.py +9 -0
- package/src/tui.py +586 -0
- package/src/web_search.py +603 -0
- package/src/wiki.py +383 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Offline coverage of frozen research inputs, separate from host execution."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ResearchCoverage:
|
|
5
|
+
"""Recheck current evidence rather than trusting a saved completion flag."""
|
|
6
|
+
|
|
7
|
+
FILTERS = ("all", "missing", "unread", "unreviewed", "blocked", "excluded", "reviewed")
|
|
8
|
+
ACTIVE_RUNS = ("pending", "queued", "in_progress", "unknown_outcome")
|
|
9
|
+
|
|
10
|
+
def __init__(self, state, read_body):
|
|
11
|
+
self.state = state
|
|
12
|
+
self.read_body = read_body
|
|
13
|
+
self.sources = {row["id"]: row for row in state["sources"]}
|
|
14
|
+
self.claims = {row["id"]: row for row in state["claims"]}
|
|
15
|
+
self.questions = {row["id"]: row for row in state["questions"]}
|
|
16
|
+
self.bodies = {}
|
|
17
|
+
|
|
18
|
+
def _body(self, identifier):
|
|
19
|
+
if identifier not in self.bodies:
|
|
20
|
+
source = self.sources.get(identifier)
|
|
21
|
+
try:
|
|
22
|
+
if source is None:
|
|
23
|
+
raise ValueError("Unknown evidence source: " + str(identifier))
|
|
24
|
+
self.bodies[identifier] = (self.read_body(source), None)
|
|
25
|
+
except (OSError, ValueError, UnicodeError) as error:
|
|
26
|
+
self.bodies[identifier] = (None, str(error))
|
|
27
|
+
return self.bodies[identifier]
|
|
28
|
+
|
|
29
|
+
def source_issues(self, identifier, inventory_id=None, accepted=False):
|
|
30
|
+
source = self.sources.get(identifier)
|
|
31
|
+
body, error = self._body(identifier)
|
|
32
|
+
issues = [error] if error else []
|
|
33
|
+
if source is None:
|
|
34
|
+
return issues
|
|
35
|
+
verdict = source.get("review", {}).get("verdict", "unreviewed")
|
|
36
|
+
if verdict == "rejected" or (accepted and verdict != "accepted"):
|
|
37
|
+
issues.append("Source %s is not accepted after content review" % identifier)
|
|
38
|
+
if inventory_id is not None:
|
|
39
|
+
if source.get("evidence_kind", "page") == "external_report":
|
|
40
|
+
issues.append("An external report does not establish that its cited original page was read")
|
|
41
|
+
if inventory_id not in source.get("inventory_ids", []):
|
|
42
|
+
issues.append("Source %s is not linked to this original URL" % identifier)
|
|
43
|
+
return issues
|
|
44
|
+
|
|
45
|
+
def citation_issues(self, citation, accepted=False):
|
|
46
|
+
identifier = citation["source_id"]
|
|
47
|
+
issues = self.source_issues(identifier, accepted=accepted)
|
|
48
|
+
body, error = self._body(identifier)
|
|
49
|
+
if not error and citation["quote"] not in body:
|
|
50
|
+
issues.append("Quote is absent from saved source " + identifier)
|
|
51
|
+
source = self.sources.get(identifier)
|
|
52
|
+
if source and citation.get("source_sha256", source["sha256"]) != source["sha256"]:
|
|
53
|
+
issues.append("Citation snapshot changed for source " + identifier)
|
|
54
|
+
return issues
|
|
55
|
+
|
|
56
|
+
def claim_issues(self, identifier, accepted=False):
|
|
57
|
+
claim = self.claims.get(identifier)
|
|
58
|
+
if claim is None:
|
|
59
|
+
return ["Unknown claim: " + str(identifier)]
|
|
60
|
+
if claim.get("status") == "retracted":
|
|
61
|
+
return ["Retracted claim: " + identifier]
|
|
62
|
+
issues = []
|
|
63
|
+
for citation in claim["citations"]:
|
|
64
|
+
issues.extend(self.citation_issues(citation, accepted=accepted))
|
|
65
|
+
if not claim["citations"]:
|
|
66
|
+
issues.append("Claim has no citations: " + identifier)
|
|
67
|
+
return issues
|
|
68
|
+
|
|
69
|
+
def review_issues(self, review):
|
|
70
|
+
if review["disposition"] != "reviewed":
|
|
71
|
+
return []
|
|
72
|
+
issues = []
|
|
73
|
+
identifier = review["inventory_id"]
|
|
74
|
+
for source_id in review["source_ids"]:
|
|
75
|
+
issues.extend(self.source_issues(source_id, inventory_id=identifier, accepted=True))
|
|
76
|
+
for question_id in review["question_ids"]:
|
|
77
|
+
if question_id not in self.questions:
|
|
78
|
+
issues.append("Unknown review question: " + question_id)
|
|
79
|
+
for citation in review["citations"]:
|
|
80
|
+
if citation["source_id"] not in review["source_ids"]:
|
|
81
|
+
issues.append("A review citation must refer to its declared original-page sources")
|
|
82
|
+
issues.extend(self.citation_issues(citation, accepted=True))
|
|
83
|
+
for claim_id in review["claim_ids"]:
|
|
84
|
+
issues.extend(self.claim_issues(claim_id, accepted=True))
|
|
85
|
+
claim = self.claims.get(claim_id)
|
|
86
|
+
if claim is not None:
|
|
87
|
+
if claim["question_id"] not in review["question_ids"]:
|
|
88
|
+
issues.append("Review claims must belong to its declared questions")
|
|
89
|
+
if not any(citation["source_id"] in review["source_ids"] for citation in claim["citations"]):
|
|
90
|
+
issues.append("A review claim must cite this original page")
|
|
91
|
+
if not review["source_ids"] or not review["question_ids"] or not (review["citations"] or review["claim_ids"]):
|
|
92
|
+
issues.append("Substantive review needs a question, accepted original-page text and a quoted judgment")
|
|
93
|
+
return list(dict.fromkeys(issues))
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _metric(count, total):
|
|
97
|
+
return {"count": count, "total": total, "rate": count / total if total else None}
|
|
98
|
+
|
|
99
|
+
def evaluate(self):
|
|
100
|
+
reviews = {row["inventory_id"]: row for row in self.state.get("inventory_reviews", [])}
|
|
101
|
+
rows = []
|
|
102
|
+
for entry in self.state.get("inventory", []):
|
|
103
|
+
identifier = entry["id"]
|
|
104
|
+
review = reviews.get(identifier)
|
|
105
|
+
source_ids = [source["id"] for source in self.sources.values()
|
|
106
|
+
if identifier in source.get("inventory_ids", [])
|
|
107
|
+
and source.get("evidence_kind", "page") != "external_report"]
|
|
108
|
+
usable_ids = [source_id for source_id in source_ids
|
|
109
|
+
if not self.source_issues(source_id, inventory_id=identifier, accepted=True)]
|
|
110
|
+
issues = self.review_issues(review) if review else []
|
|
111
|
+
disposition = review["disposition"] if review else "unreviewed"
|
|
112
|
+
excluded = disposition == "excluded"
|
|
113
|
+
substantive = disposition == "reviewed" and not issues
|
|
114
|
+
accounted = bool(review or source_ids)
|
|
115
|
+
rows.append({"id": identifier, "original_url": entry["original_url"],
|
|
116
|
+
"source_ids": source_ids, "usable_source_ids": usable_ids,
|
|
117
|
+
"disposition": disposition, "review": review, "issues": issues,
|
|
118
|
+
"accounted_for": accounted, "usable_text": bool(usable_ids),
|
|
119
|
+
"substantive_review": substantive,
|
|
120
|
+
"missing": not accounted, "unread": not excluded and not usable_ids,
|
|
121
|
+
"unreviewed": not excluded and not substantive,
|
|
122
|
+
"blocked": disposition == "blocked" or bool(issues),
|
|
123
|
+
"excluded": excluded, "reviewed": substantive})
|
|
124
|
+
question_rows = []
|
|
125
|
+
for question in self.questions.values():
|
|
126
|
+
issues = []
|
|
127
|
+
if question["status"] == "answered":
|
|
128
|
+
if not question["claim_ids"]:
|
|
129
|
+
issues.append("The answer has no supporting claim")
|
|
130
|
+
for claim_id in question["claim_ids"]:
|
|
131
|
+
issues.extend(self.claim_issues(claim_id))
|
|
132
|
+
question_rows.append({"id": question["id"], "status": question["status"],
|
|
133
|
+
"complete": question["status"] == "answered" and not issues,
|
|
134
|
+
"gap": question.get("gap"), "issues": list(dict.fromkeys(issues))})
|
|
135
|
+
metrics = {key: self._metric(sum(bool(row[key]) for row in rows), len(rows))
|
|
136
|
+
for key in ("accounted_for", "usable_text", "substantive_review")}
|
|
137
|
+
metrics["question_completion"] = self._metric(sum(row["complete"] for row in question_rows), len(question_rows))
|
|
138
|
+
scope = self.state.get("source_scope")
|
|
139
|
+
blockers = []
|
|
140
|
+
if scope is None and self.state.get("source_ids"):
|
|
141
|
+
blockers.append("Legacy session has no frozen inventory; whole-input coverage is unavailable")
|
|
142
|
+
if rows:
|
|
143
|
+
if any(row["unreviewed"] for row in rows):
|
|
144
|
+
blockers.append("Original input URLs still need substantive review")
|
|
145
|
+
if not any(row["substantive_review"] for row in rows):
|
|
146
|
+
blockers.append("No original input has a substantive, evidence-backed review")
|
|
147
|
+
if any(not row["complete"] for row in question_rows):
|
|
148
|
+
blockers.append("Research questions remain unanswered or their evidence is invalid")
|
|
149
|
+
if any(row["status"] == "open" for row in self.state["conflicts"]):
|
|
150
|
+
blockers.append("Unresolved evidence conflicts remain")
|
|
151
|
+
if any(row["status"] == "pending" for row in self.state["operations"]):
|
|
152
|
+
blockers.append("An interrupted operation needs acknowledgement")
|
|
153
|
+
if any(row["status"] in self.ACTIVE_RUNS for row in self.state.get("external_runs", [])):
|
|
154
|
+
blockers.append("An external run still has a pending or unknown outcome")
|
|
155
|
+
return {"available": scope is not None, "source_scope": scope,
|
|
156
|
+
"legacy_note": None if scope is not None else "No input snapshot was captured by this legacy session; no scope is inferred from its previews or labels.",
|
|
157
|
+
"metrics": metrics, "difference_counts": {key: sum(bool(row[key]) for row in rows)
|
|
158
|
+
for key in self.FILTERS if key != "all"},
|
|
159
|
+
"completion_ready": not blockers, "completion_blockers": blockers,
|
|
160
|
+
"questions": question_rows, "rows": rows}
|