ctxora 6.2.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/LICENSE +21 -0
- package/README.md +441 -0
- package/README.vi.md +441 -0
- package/bin/ctxora.mjs +147 -0
- package/package.json +45 -0
- package/pyproject.toml +59 -0
- package/src/chunking/compressor.py +104 -0
- package/src/chunking/treesitter_chunker.py +240 -0
- package/src/compact/anthropic.py +98 -0
- package/src/compact/gemini.py +88 -0
- package/src/compact/handoff.py +179 -0
- package/src/compact/openai.py +318 -0
- package/src/compact/summarizer.py +186 -0
- package/src/context/assembler.py +298 -0
- package/src/context/budgeting.py +137 -0
- package/src/context/sanitizer.py +23 -0
- package/src/evaluation/__init__.py +1 -0
- package/src/evaluation/gates.py +172 -0
- package/src/evaluation/metrics.py +41 -0
- package/src/harness_context/__init__.py +5 -0
- package/src/harness_context/adapters/__init__.py +1 -0
- package/src/harness_context/adapters/clients/__init__.py +4 -0
- package/src/harness_context/adapters/clients/formatters.py +47 -0
- package/src/harness_context/adapters/clients/profiles.py +29 -0
- package/src/harness_context/adapters/ecc/__init__.py +4 -0
- package/src/harness_context/adapters/ecc/detection.py +41 -0
- package/src/harness_context/adapters/ecc/mapping.py +32 -0
- package/src/harness_context/adapters/ecc/memory_reader.py +162 -0
- package/src/harness_context/adapters/ecc/provenance.py +16 -0
- package/src/harness_context/api/__init__.py +1 -0
- package/src/harness_context/api/v2/__init__.py +12 -0
- package/src/harness_context/api/v2/contracts.py +119 -0
- package/src/harness_context/api/v2/diagnostics.py +13 -0
- package/src/harness_context/api/v2/enums.py +17 -0
- package/src/harness_context/api/v2/errors.py +32 -0
- package/src/harness_context/api/v2/models.py +4 -0
- package/src/harness_context/api/v2/requests.py +17 -0
- package/src/harness_context/api/v2/responses.py +22 -0
- package/src/harness_context/application/__init__.py +3 -0
- package/src/harness_context/application/container.py +31 -0
- package/src/harness_context/application/context_service.py +51 -0
- package/src/harness_context/application/ecc_service.py +7 -0
- package/src/harness_context/application/handoff_service.py +11 -0
- package/src/harness_context/application/memory_service.py +9 -0
- package/src/harness_context/application/protocols.py +46 -0
- package/src/harness_context/application/refresh_service.py +25 -0
- package/src/harness_context/application/retrieval_service.py +22 -0
- package/src/harness_context/application/services.py +4 -0
- package/src/harness_context/application/workspace_service.py +18 -0
- package/src/harness_context/bootstrap.py +47 -0
- package/src/harness_context/branding.py +16 -0
- package/src/harness_context/cli/__init__.py +1 -0
- package/src/harness_context/cli/app.py +239 -0
- package/src/harness_context/cli/exit_codes.py +25 -0
- package/src/harness_context/domain/__init__.py +9 -0
- package/src/harness_context/domain/cag.py +18 -0
- package/src/harness_context/domain/chunking.py +17 -0
- package/src/harness_context/domain/planning.py +30 -0
- package/src/harness_context/domain/ports.py +24 -0
- package/src/harness_context/domain/retrieval.py +46 -0
- package/src/harness_context/engine.py +10 -0
- package/src/harness_context/free_tools.py +143 -0
- package/src/harness_context/infrastructure/__init__.py +10 -0
- package/src/harness_context/infrastructure/graph.py +26 -0
- package/src/harness_context/infrastructure/indexes.py +33 -0
- package/src/harness_context/infrastructure/local_engine.py +296 -0
- package/src/harness_context/infrastructure/parsing.py +38 -0
- package/src/harness_context/infrastructure/scanning.py +51 -0
- package/src/harness_context/installer/__init__.py +4 -0
- package/src/harness_context/installer/models.py +22 -0
- package/src/harness_context/installer/service.py +168 -0
- package/src/harness_context/mcp/__init__.py +3 -0
- package/src/harness_context/mcp/capabilities.py +11 -0
- package/src/harness_context/mcp/errors.py +8 -0
- package/src/harness_context/mcp/lifecycle.py +72 -0
- package/src/harness_context/mcp/middleware.py +57 -0
- package/src/harness_context/mcp/server.py +3 -0
- package/src/harness_context/mcp/tool_handlers/__init__.py +7 -0
- package/src/harness_context/mcp/tool_handlers/context.py +16 -0
- package/src/harness_context/mcp/tool_handlers/ecc.py +8 -0
- package/src/harness_context/mcp/tool_handlers/handoffs.py +20 -0
- package/src/harness_context/mcp/tool_handlers/memory.py +16 -0
- package/src/harness_context/mcp/tool_handlers/workspace.py +12 -0
- package/src/harness_context/mcp/tools.py +15 -0
- package/src/harness_context/observability/__init__.py +6 -0
- package/src/harness_context/observability/events.py +25 -0
- package/src/harness_context/observability/metrics.py +20 -0
- package/src/harness_context/paths.py +35 -0
- package/src/harness_context/runtime.py +127 -0
- package/src/harness_context/schemas.py +38 -0
- package/src/harness_context/security/__init__.py +3 -0
- package/src/harness_context/security/secret_patterns.py +15 -0
- package/src/harness_context/server.py +1077 -0
- package/src/harness_context/storage/__init__.py +6 -0
- package/src/harness_context/storage/migrations.py +24 -0
- package/src/harness_context/storage/pins.py +10 -0
- package/src/harness_context/storage/snapshots.py +149 -0
- package/src/harness_context/tokenize.py +12 -0
- package/src/harness_context/topology.py +65 -0
- package/src/harness_context/watcher/__init__.py +3 -0
- package/src/harness_context/watcher/service.py +32 -0
- package/src/harness_context/workspace/__init__.py +13 -0
- package/src/harness_context/workspace/identity.py +9 -0
- package/src/harness_context/workspace/lock.py +24 -0
- package/src/harness_context/workspace/policy.py +3 -0
- package/src/harness_context/workspace/roots.py +84 -0
- package/src/harness_context/workspace/state.py +35 -0
- package/src/memory/episodic.py +257 -0
- package/src/memory/vector_store.py +104 -0
- package/src/retrieval/bm25.py +23 -0
- package/src/retrieval/cache.py +76 -0
- package/src/retrieval/embeddings.py +75 -0
- package/src/retrieval/graph.py +45 -0
- package/src/retrieval/reranker.py +78 -0
- package/src/retrieval/tokenize.py +11 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import tomllib
|
|
9
|
+
except ModuleNotFoundError: # Python 3.10
|
|
10
|
+
import tomli as tomllib
|
|
11
|
+
|
|
12
|
+
from harness_context.application.container import ApplicationContainer
|
|
13
|
+
from harness_context.bootstrap import build_container, workspace_identity
|
|
14
|
+
from harness_context.mcp.lifecycle import ServerLifecycle
|
|
15
|
+
from harness_context.paths import legacy_workspace_data_dir, workspace_data_dir
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class RuntimeConfig:
|
|
20
|
+
workspace: Path
|
|
21
|
+
workspace_id: str
|
|
22
|
+
transport: str = "stdio"
|
|
23
|
+
host: str = "127.0.0.1"
|
|
24
|
+
port: int = 8765
|
|
25
|
+
startup_policy: str = "block_until_ready"
|
|
26
|
+
watch: bool = False
|
|
27
|
+
ecc_enabled: bool = False
|
|
28
|
+
ecc_allow_user_scope: bool = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HarnessRuntime:
|
|
32
|
+
def __init__(self, config: RuntimeConfig):
|
|
33
|
+
self.config = config
|
|
34
|
+
self.container: ApplicationContainer | None = None
|
|
35
|
+
self.lifecycle = ServerLifecycle()
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def for_workspace(
|
|
39
|
+
cls, workspace: str, transport: str | None = None,
|
|
40
|
+
host: str | None = None, port: int | None = None, watch: bool | None = None,
|
|
41
|
+
ecc_enabled: bool | None = None, ecc_allow_user_scope: bool | None = None,
|
|
42
|
+
) -> HarnessRuntime:
|
|
43
|
+
root = Path(workspace).expanduser().resolve(strict=True)
|
|
44
|
+
values = {"transport": "stdio", "host": "127.0.0.1", "port": 8765, "startup_policy": "block_until_ready", "watch": False, "ecc_enabled": False, "ecc_allow_user_scope": False}
|
|
45
|
+
for path in (
|
|
46
|
+
Path.home() / ".config" / "harness" / "config.toml",
|
|
47
|
+
Path.home() / ".config" / "ctxora" / "config.toml",
|
|
48
|
+
legacy_workspace_data_dir(root) / "config.toml",
|
|
49
|
+
workspace_data_dir(root) / "config.toml",
|
|
50
|
+
):
|
|
51
|
+
if path.exists():
|
|
52
|
+
loaded = tomllib.loads(path.read_text("utf-8"))
|
|
53
|
+
unknown = set(loaded) - set(values)
|
|
54
|
+
if unknown:
|
|
55
|
+
raise ValueError(f"unknown runtime config keys: {sorted(unknown)}")
|
|
56
|
+
values.update(loaded)
|
|
57
|
+
def environment_value(name: str) -> str | None:
|
|
58
|
+
return os.environ.get(f"CTXORA_{name}") or os.environ.get(f"HARNESS_{name}")
|
|
59
|
+
|
|
60
|
+
environment = {
|
|
61
|
+
"transport": environment_value("TRANSPORT"),
|
|
62
|
+
"host": environment_value("HOST"),
|
|
63
|
+
"port": environment_value("PORT"),
|
|
64
|
+
"ecc_enabled": environment_value("ECC_ENABLED"),
|
|
65
|
+
"ecc_allow_user_scope": environment_value("ECC_ALLOW_USER_SCOPE"),
|
|
66
|
+
}
|
|
67
|
+
values.update({key: value for key, value in environment.items() if value not in {None, ""}})
|
|
68
|
+
overrides = {"transport": transport, "host": host, "port": port, "watch": watch, "ecc_enabled": ecc_enabled, "ecc_allow_user_scope": ecc_allow_user_scope}
|
|
69
|
+
values.update({key: value for key, value in overrides.items() if value is not None})
|
|
70
|
+
def boolean(value) -> bool:
|
|
71
|
+
return value.casefold() in {"1", "true", "yes", "on"} if isinstance(value, str) else bool(value)
|
|
72
|
+
user_scope = boolean(values["ecc_allow_user_scope"])
|
|
73
|
+
return cls(RuntimeConfig(
|
|
74
|
+
root, workspace_identity(root), str(values["transport"]), str(values["host"]),
|
|
75
|
+
int(values["port"]), str(values["startup_policy"]), boolean(values["watch"]),
|
|
76
|
+
boolean(values["ecc_enabled"]) or user_scope, user_scope,
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
def startup(self) -> dict:
|
|
80
|
+
self.container = build_container(
|
|
81
|
+
self.config.workspace, self.config.workspace_id,
|
|
82
|
+
ecc_enabled=self.config.ecc_enabled,
|
|
83
|
+
ecc_allow_user_scope=self.config.ecc_allow_user_scope,
|
|
84
|
+
)
|
|
85
|
+
warm = self.container.retrieval.snapshot_is_current(self.config.workspace_id)
|
|
86
|
+
if not warm:
|
|
87
|
+
refresh = self.container.refresh.execute(self.config.workspace_id)
|
|
88
|
+
else:
|
|
89
|
+
refresh = self.container.retrieval.stats(self.config.workspace_id)
|
|
90
|
+
self.lifecycle.mark_ready()
|
|
91
|
+
return {"warm_start": warm, **refresh}
|
|
92
|
+
|
|
93
|
+
def health_report(self) -> dict[str, object]:
|
|
94
|
+
lifecycle = self.lifecycle.status()
|
|
95
|
+
stats = self.container.retrieval.stats(self.config.workspace_id) if self.container else {}
|
|
96
|
+
ready = bool(lifecycle["ready"] and stats.get("status") == "ready")
|
|
97
|
+
return {
|
|
98
|
+
"schema_version": 1, "status": "ready" if ready else "not_ready",
|
|
99
|
+
"ready": ready, "draining": lifecycle["draining"],
|
|
100
|
+
"active_requests": lifecycle["active_requests"],
|
|
101
|
+
"snapshot_version": int(stats.get("snapshot_version", 0)),
|
|
102
|
+
"files": int(stats.get("files", 0)), "chunks": int(stats.get("chunks", 0)),
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def drain(self, timeout: float = 10.0) -> bool:
|
|
106
|
+
return self.lifecycle.drain(timeout)
|
|
107
|
+
|
|
108
|
+
def run(self) -> None:
|
|
109
|
+
if self.container is None:
|
|
110
|
+
self.startup()
|
|
111
|
+
from harness_context.mcp.server import create_mcp_server
|
|
112
|
+
from harness_context.watcher import WorkspaceWatcher
|
|
113
|
+
server = create_mcp_server(self.container, self.config.workspace_id, self.lifecycle)
|
|
114
|
+
watcher = WorkspaceWatcher(self.container.retrieval, self.container.refresh, self.config.workspace_id) if self.config.watch else None
|
|
115
|
+
if watcher:
|
|
116
|
+
watcher.start()
|
|
117
|
+
try:
|
|
118
|
+
if self.config.transport == "streamable-http":
|
|
119
|
+
server.settings.host = self.config.host
|
|
120
|
+
server.settings.port = self.config.port
|
|
121
|
+
server.run(transport="streamable-http")
|
|
122
|
+
return
|
|
123
|
+
server.run(transport="stdio")
|
|
124
|
+
finally:
|
|
125
|
+
self.drain()
|
|
126
|
+
if watcher:
|
|
127
|
+
watcher.stop()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class WorkspacePolicy:
|
|
9
|
+
roots: tuple[str, ...]
|
|
10
|
+
max_files: int = 10_000
|
|
11
|
+
max_file_bytes: int = 2_000_000
|
|
12
|
+
max_total_bytes: int = 200_000_000
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class ContextItem:
|
|
17
|
+
chunk_id: str
|
|
18
|
+
workspace_id: str
|
|
19
|
+
path: str
|
|
20
|
+
start_line: int
|
|
21
|
+
end_line: int
|
|
22
|
+
type: str
|
|
23
|
+
symbol: str
|
|
24
|
+
content: str
|
|
25
|
+
tokens: int
|
|
26
|
+
content_hash: str
|
|
27
|
+
score: float = 0.0
|
|
28
|
+
signals: dict[str, float] = field(default_factory=dict)
|
|
29
|
+
provenance: str = "workspace"
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> dict[str, Any]:
|
|
32
|
+
return asdict(self)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class HarnessError(ValueError):
|
|
36
|
+
def __init__(self, code: str, message: str):
|
|
37
|
+
super().__init__(message)
|
|
38
|
+
self.code = code
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
_PATTERNS = (
|
|
6
|
+
re.compile(rb"\bsk-[A-Za-z0-9_-]{16,}\b", re.IGNORECASE),
|
|
7
|
+
re.compile(rb"\b(?:sk|rk)_live_[A-Za-z0-9]{16,}\b"),
|
|
8
|
+
re.compile(rb"\b(?:gh[pors]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{16,})\b"),
|
|
9
|
+
re.compile(rb"\b(?:AKIA|ASIA)[A-Z0-9]{16}\b"),
|
|
10
|
+
re.compile(rb"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----"),
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def contains_secret(content: bytes) -> bool:
|
|
15
|
+
return any(pattern.search(content) for pattern in _PATTERNS)
|