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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +441 -0
  3. package/README.vi.md +441 -0
  4. package/bin/ctxora.mjs +147 -0
  5. package/package.json +45 -0
  6. package/pyproject.toml +59 -0
  7. package/src/chunking/compressor.py +104 -0
  8. package/src/chunking/treesitter_chunker.py +240 -0
  9. package/src/compact/anthropic.py +98 -0
  10. package/src/compact/gemini.py +88 -0
  11. package/src/compact/handoff.py +179 -0
  12. package/src/compact/openai.py +318 -0
  13. package/src/compact/summarizer.py +186 -0
  14. package/src/context/assembler.py +298 -0
  15. package/src/context/budgeting.py +137 -0
  16. package/src/context/sanitizer.py +23 -0
  17. package/src/evaluation/__init__.py +1 -0
  18. package/src/evaluation/gates.py +172 -0
  19. package/src/evaluation/metrics.py +41 -0
  20. package/src/harness_context/__init__.py +5 -0
  21. package/src/harness_context/adapters/__init__.py +1 -0
  22. package/src/harness_context/adapters/clients/__init__.py +4 -0
  23. package/src/harness_context/adapters/clients/formatters.py +47 -0
  24. package/src/harness_context/adapters/clients/profiles.py +29 -0
  25. package/src/harness_context/adapters/ecc/__init__.py +4 -0
  26. package/src/harness_context/adapters/ecc/detection.py +41 -0
  27. package/src/harness_context/adapters/ecc/mapping.py +32 -0
  28. package/src/harness_context/adapters/ecc/memory_reader.py +162 -0
  29. package/src/harness_context/adapters/ecc/provenance.py +16 -0
  30. package/src/harness_context/api/__init__.py +1 -0
  31. package/src/harness_context/api/v2/__init__.py +12 -0
  32. package/src/harness_context/api/v2/contracts.py +119 -0
  33. package/src/harness_context/api/v2/diagnostics.py +13 -0
  34. package/src/harness_context/api/v2/enums.py +17 -0
  35. package/src/harness_context/api/v2/errors.py +32 -0
  36. package/src/harness_context/api/v2/models.py +4 -0
  37. package/src/harness_context/api/v2/requests.py +17 -0
  38. package/src/harness_context/api/v2/responses.py +22 -0
  39. package/src/harness_context/application/__init__.py +3 -0
  40. package/src/harness_context/application/container.py +31 -0
  41. package/src/harness_context/application/context_service.py +51 -0
  42. package/src/harness_context/application/ecc_service.py +7 -0
  43. package/src/harness_context/application/handoff_service.py +11 -0
  44. package/src/harness_context/application/memory_service.py +9 -0
  45. package/src/harness_context/application/protocols.py +46 -0
  46. package/src/harness_context/application/refresh_service.py +25 -0
  47. package/src/harness_context/application/retrieval_service.py +22 -0
  48. package/src/harness_context/application/services.py +4 -0
  49. package/src/harness_context/application/workspace_service.py +18 -0
  50. package/src/harness_context/bootstrap.py +47 -0
  51. package/src/harness_context/branding.py +16 -0
  52. package/src/harness_context/cli/__init__.py +1 -0
  53. package/src/harness_context/cli/app.py +239 -0
  54. package/src/harness_context/cli/exit_codes.py +25 -0
  55. package/src/harness_context/domain/__init__.py +9 -0
  56. package/src/harness_context/domain/cag.py +18 -0
  57. package/src/harness_context/domain/chunking.py +17 -0
  58. package/src/harness_context/domain/planning.py +30 -0
  59. package/src/harness_context/domain/ports.py +24 -0
  60. package/src/harness_context/domain/retrieval.py +46 -0
  61. package/src/harness_context/engine.py +10 -0
  62. package/src/harness_context/free_tools.py +143 -0
  63. package/src/harness_context/infrastructure/__init__.py +10 -0
  64. package/src/harness_context/infrastructure/graph.py +26 -0
  65. package/src/harness_context/infrastructure/indexes.py +33 -0
  66. package/src/harness_context/infrastructure/local_engine.py +296 -0
  67. package/src/harness_context/infrastructure/parsing.py +38 -0
  68. package/src/harness_context/infrastructure/scanning.py +51 -0
  69. package/src/harness_context/installer/__init__.py +4 -0
  70. package/src/harness_context/installer/models.py +22 -0
  71. package/src/harness_context/installer/service.py +168 -0
  72. package/src/harness_context/mcp/__init__.py +3 -0
  73. package/src/harness_context/mcp/capabilities.py +11 -0
  74. package/src/harness_context/mcp/errors.py +8 -0
  75. package/src/harness_context/mcp/lifecycle.py +72 -0
  76. package/src/harness_context/mcp/middleware.py +57 -0
  77. package/src/harness_context/mcp/server.py +3 -0
  78. package/src/harness_context/mcp/tool_handlers/__init__.py +7 -0
  79. package/src/harness_context/mcp/tool_handlers/context.py +16 -0
  80. package/src/harness_context/mcp/tool_handlers/ecc.py +8 -0
  81. package/src/harness_context/mcp/tool_handlers/handoffs.py +20 -0
  82. package/src/harness_context/mcp/tool_handlers/memory.py +16 -0
  83. package/src/harness_context/mcp/tool_handlers/workspace.py +12 -0
  84. package/src/harness_context/mcp/tools.py +15 -0
  85. package/src/harness_context/observability/__init__.py +6 -0
  86. package/src/harness_context/observability/events.py +25 -0
  87. package/src/harness_context/observability/metrics.py +20 -0
  88. package/src/harness_context/paths.py +35 -0
  89. package/src/harness_context/runtime.py +127 -0
  90. package/src/harness_context/schemas.py +38 -0
  91. package/src/harness_context/security/__init__.py +3 -0
  92. package/src/harness_context/security/secret_patterns.py +15 -0
  93. package/src/harness_context/server.py +1077 -0
  94. package/src/harness_context/storage/__init__.py +6 -0
  95. package/src/harness_context/storage/migrations.py +24 -0
  96. package/src/harness_context/storage/pins.py +10 -0
  97. package/src/harness_context/storage/snapshots.py +149 -0
  98. package/src/harness_context/tokenize.py +12 -0
  99. package/src/harness_context/topology.py +65 -0
  100. package/src/harness_context/watcher/__init__.py +3 -0
  101. package/src/harness_context/watcher/service.py +32 -0
  102. package/src/harness_context/workspace/__init__.py +13 -0
  103. package/src/harness_context/workspace/identity.py +9 -0
  104. package/src/harness_context/workspace/lock.py +24 -0
  105. package/src/harness_context/workspace/policy.py +3 -0
  106. package/src/harness_context/workspace/roots.py +84 -0
  107. package/src/harness_context/workspace/state.py +35 -0
  108. package/src/memory/episodic.py +257 -0
  109. package/src/memory/vector_store.py +104 -0
  110. package/src/retrieval/bm25.py +23 -0
  111. package/src/retrieval/cache.py +76 -0
  112. package/src/retrieval/embeddings.py +75 -0
  113. package/src/retrieval/graph.py +45 -0
  114. package/src/retrieval/reranker.py +78 -0
  115. package/src/retrieval/tokenize.py +11 -0
@@ -0,0 +1,4 @@
1
+ from harness_context.adapters.clients.formatters import get_formatter
2
+ from harness_context.adapters.clients.profiles import PROFILES, ClientProfile, get_profile
3
+
4
+ __all__ = ["PROFILES", "ClientProfile", "get_formatter", "get_profile"]
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from dataclasses import asdict, is_dataclass
5
+ from typing import Any
6
+
7
+
8
+ class ProviderFormatter:
9
+ def format(self, payload: Any) -> dict[str, Any]:
10
+ if hasattr(payload, "to_dict"):
11
+ payload = payload.to_dict()
12
+ elif is_dataclass(payload):
13
+ payload = asdict(payload)
14
+ if not isinstance(payload, dict):
15
+ raise TypeError("provider payload must be a mapping or dataclass")
16
+ return deepcopy(payload)
17
+
18
+
19
+ class CodexFormatter(ProviderFormatter):
20
+ pass
21
+
22
+
23
+ class ClaudeCodeFormatter(ProviderFormatter):
24
+ pass
25
+
26
+
27
+ class CursorFormatter(ProviderFormatter):
28
+ pass
29
+
30
+
31
+ class GenericMcpFormatter(ProviderFormatter):
32
+ pass
33
+
34
+
35
+ FORMATTERS = {
36
+ "codex": CodexFormatter,
37
+ "claude-code": ClaudeCodeFormatter,
38
+ "cursor": CursorFormatter,
39
+ "generic-mcp": GenericMcpFormatter,
40
+ }
41
+
42
+
43
+ def get_formatter(name: str) -> ProviderFormatter:
44
+ try:
45
+ return FORMATTERS[name]()
46
+ except KeyError as exc:
47
+ raise ValueError(f"unknown provider formatter: {name}") from exc
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from harness_context.branding import MCP_SERVER_KEY
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ClientProfile:
11
+ name: str
12
+ config_format: str
13
+ default_config: Path
14
+ server_key: str = MCP_SERVER_KEY
15
+
16
+
17
+ PROFILES = {
18
+ "codex": ClientProfile("codex", "toml", Path("~/.codex/config.toml")),
19
+ "claude-code": ClientProfile("claude-code", "json", Path("~/.claude.json")),
20
+ "cursor": ClientProfile("cursor", "json", Path("~/.cursor/mcp.json")),
21
+ "generic-mcp": ClientProfile("generic-mcp", "json", Path("~/.config/mcp/servers.json")),
22
+ }
23
+
24
+
25
+ def get_profile(name: str) -> ClientProfile:
26
+ try:
27
+ return PROFILES[name]
28
+ except KeyError as exc:
29
+ raise ValueError(f"unknown client profile: {name}") from exc
@@ -0,0 +1,4 @@
1
+ from harness_context.adapters.ecc.detection import EccInstallation, detect_ecc
2
+ from harness_context.adapters.ecc.memory_reader import EccMemoryReader
3
+
4
+ __all__ = ["EccInstallation", "EccMemoryReader", "detect_ecc"]
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class EccInstallation:
10
+ project_memory_root: Path | None
11
+ user_memory_root: Path | None
12
+ allow_user_scope: bool
13
+
14
+ @property
15
+ def available(self) -> bool:
16
+ return self.project_memory_root is not None or self.user_memory_root is not None
17
+
18
+
19
+ def _existing_directory(path: str | Path) -> Path | None:
20
+ candidate = Path(path).expanduser()
21
+ if candidate.is_symlink() or not candidate.is_dir():
22
+ return None
23
+ return candidate.resolve()
24
+
25
+
26
+ def detect_ecc(
27
+ workspace_root: str | Path,
28
+ *,
29
+ allow_user_scope: bool = False,
30
+ environment: dict[str, str] | None = None,
31
+ ) -> EccInstallation:
32
+ """Detect existing ECC memory vaults without installing or cloning ECC."""
33
+ env = environment if environment is not None else os.environ
34
+ workspace = Path(workspace_root).expanduser().resolve(strict=True)
35
+ project_override = env.get("ECC_MEMORY_PROJECT_ROOT")
36
+ user_override = env.get("ECC_MEMORY_USER_ROOT")
37
+ project_root = _existing_directory(project_override or workspace / ".ecc" / "memory")
38
+ user_root = None
39
+ if allow_user_scope:
40
+ user_root = _existing_directory(user_override or Path.home() / ".ecc" / "memory")
41
+ return EccInstallation(project_root, user_root, allow_user_scope)
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from harness_context.adapters.ecc.provenance import ecc_provenance
4
+
5
+ _TYPE_MAP = {
6
+ "handoff": "episodic",
7
+ "lesson": "procedural",
8
+ "runbook": "procedural",
9
+ "context": "semantic",
10
+ "decision": "semantic",
11
+ "fact": "semantic",
12
+ "note": "semantic",
13
+ "preference": "semantic",
14
+ }
15
+
16
+
17
+ def map_ecc_memory(memory: dict, source_path) -> dict:
18
+ """Map ECC context into Harness's provider-neutral read-only memory shape."""
19
+ return {
20
+ "workspace_id": "external:ecc",
21
+ "type": _TYPE_MAP[memory["kind"]],
22
+ "key": memory["id"],
23
+ "value": memory["body"],
24
+ "title": memory["title"],
25
+ "tags": ",".join(memory["tags"]),
26
+ "scope": memory["scope"],
27
+ "source": f"ecc:{memory['sourceHarness']}",
28
+ "confidence": 0.5,
29
+ "status": memory["status"],
30
+ "links": list(memory["links"]),
31
+ "provenance": ecc_provenance(memory, source_path),
32
+ }
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+
7
+ from harness_context.adapters.ecc.detection import EccInstallation
8
+ from harness_context.adapters.ecc.mapping import map_ecc_memory
9
+ from harness_context.tokenize import tokens
10
+
11
+ _FIELDS = {
12
+ "schema": "schema", "id": "id", "title": "title", "kind": "kind",
13
+ "scope": "scope", "trust": "trust", "status": "status",
14
+ "source_harness": "sourceHarness", "target_harnesses": "targetHarnesses",
15
+ "tags": "tags", "links": "links", "created_at": "createdAt",
16
+ "updated_at": "updatedAt",
17
+ }
18
+ _KINDS = {"context", "decision", "fact", "handoff", "lesson", "note", "preference", "runbook"}
19
+ _SCOPES = {"project", "team", "user"}
20
+ _STATUSES = {"active", "rejected", "superseded"}
21
+ _ID = re.compile(r"^mem_[a-z0-9][a-z0-9_-]{2,127}$")
22
+ _SLUG = re.compile(r"^[a-z0-9][a-z0-9._-]{0,63}$")
23
+ _TIMESTAMP = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$")
24
+ _SECRETS = (
25
+ re.compile(r"\bsk-[A-Za-z0-9_-]{16,}\b", re.IGNORECASE),
26
+ re.compile(r"\b(?:sk|rk)_live_[A-Za-z0-9]{16,}\b"),
27
+ re.compile(r"\bnpm_[A-Za-z0-9]{20,}\b"),
28
+ re.compile(r"\bhf_[A-Za-z0-9]{20,}\b"),
29
+ re.compile(r"\bgh[pors]_[A-Za-z0-9]{16,}\b"),
30
+ re.compile(r"\bgithub_pat_[A-Za-z0-9_]{16,}\b"),
31
+ re.compile(r"\bAIza[A-Za-z0-9_-]{16,}\b"),
32
+ re.compile(r"\bxox[baprs]-[A-Za-z0-9-]{10,}\b"),
33
+ re.compile(r"\b(?:AKIA|ASIA)[A-Z0-9]{16}\b"),
34
+ re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----"),
35
+ )
36
+
37
+
38
+ def _has_unsafe_characters(value: str, allow_whitespace: bool = False) -> bool:
39
+ for character in value:
40
+ code = ord(character)
41
+ if allow_whitespace and character in "\t\n\r":
42
+ continue
43
+ if code <= 0x1F or 0x7F <= code <= 0x9F or 0x202A <= code <= 0x202E or 0x2066 <= code <= 0x2069:
44
+ return True
45
+ return False
46
+
47
+
48
+ class EccMemoryReader:
49
+ """Bounded, read-only reader for ECC's ecc.memory.v1 Markdown vault."""
50
+
51
+ def __init__(self, installation: EccInstallation, target_harness: str = "ctxora"):
52
+ self.installation = installation
53
+ self.target_harness = target_harness
54
+ self.target_harnesses = {target_harness, "harness-context"}
55
+
56
+ def status(self) -> dict:
57
+ roots = [root for root in (self.installation.project_memory_root, self.installation.user_memory_root) if root]
58
+ return {
59
+ "available": self.installation.available,
60
+ "read_only": True,
61
+ "project_memory_root": str(self.installation.project_memory_root) if self.installation.project_memory_root else None,
62
+ "user_memory_root": str(self.installation.user_memory_root) if self.installation.user_memory_root else None,
63
+ "roots": len(roots),
64
+ }
65
+
66
+ @staticmethod
67
+ def _parse(path: Path) -> dict:
68
+ if path.is_symlink() or path.stat().st_size > 128 * 1024:
69
+ raise ValueError("ECC memory file is unsafe or oversized")
70
+ source = path.read_text("utf-8")
71
+ if not source.startswith("---\n"):
72
+ raise ValueError("ECC memory document must start with frontmatter")
73
+ frontmatter, separator, body = source[4:].partition("\n---\n")
74
+ if not separator:
75
+ raise ValueError("ECC memory document has no closing frontmatter")
76
+ parsed = {}
77
+ for line in frontmatter.splitlines():
78
+ key, marker, value = line.partition(":")
79
+ if not marker or key not in _FIELDS or _FIELDS[key] in parsed:
80
+ raise ValueError("invalid ECC memory frontmatter")
81
+ parsed[_FIELDS[key]] = json.loads(value.strip())
82
+ required = set(_FIELDS.values())
83
+ if set(parsed) != required:
84
+ raise ValueError("incomplete ECC memory frontmatter")
85
+ parsed["body"] = body.strip()
86
+ if parsed["schema"] != "ecc.memory.v1" or not _ID.fullmatch(parsed["id"]):
87
+ raise ValueError("unsupported ECC memory schema or id")
88
+ if not isinstance(parsed["title"], str) or not parsed["title"].strip() or len(parsed["title"]) > 200:
89
+ raise ValueError("invalid ECC memory title")
90
+ if _has_unsafe_characters(parsed["title"]):
91
+ raise ValueError("unsafe ECC memory title")
92
+ if parsed["kind"] not in _KINDS or parsed["scope"] not in _SCOPES:
93
+ raise ValueError("unsupported ECC memory kind or scope")
94
+ if parsed["trust"] != "unreviewed" or parsed["status"] not in _STATUSES:
95
+ raise ValueError("unsupported ECC memory trust or status")
96
+ if not _SLUG.fullmatch(parsed["sourceHarness"]):
97
+ raise ValueError("invalid ECC source harness")
98
+ if not _TIMESTAMP.fullmatch(parsed["createdAt"]) or not _TIMESTAMP.fullmatch(parsed["updatedAt"]):
99
+ raise ValueError("invalid ECC timestamp")
100
+ if not parsed["body"] or len(parsed["body"].encode("utf-8")) > 64 * 1024:
101
+ raise ValueError("invalid ECC memory body")
102
+ if _has_unsafe_characters(parsed["body"], allow_whitespace=True):
103
+ raise ValueError("unsafe ECC memory body")
104
+ if any(pattern.search(parsed["body"]) for pattern in _SECRETS):
105
+ raise ValueError("ECC memory contains a secret-like value")
106
+ for field in ("targetHarnesses", "tags"):
107
+ if not isinstance(parsed[field], list) or not all(isinstance(value, str) and _SLUG.fullmatch(value) for value in parsed[field]):
108
+ raise ValueError(f"invalid ECC {field}")
109
+ if len(parsed[field]) != len(set(parsed[field])):
110
+ raise ValueError(f"duplicate ECC {field}")
111
+ if not parsed["targetHarnesses"] or len(parsed["targetHarnesses"]) > 32 or len(parsed["tags"]) > 32:
112
+ raise ValueError("invalid ECC target or tag count")
113
+ if not isinstance(parsed["links"], list) or not all(_ID.fullmatch(value) for value in parsed["links"]):
114
+ raise ValueError("invalid ECC links")
115
+ if len(parsed["links"]) > 64:
116
+ raise ValueError("too many ECC links")
117
+ if len(parsed["links"]) != len(set(parsed["links"])):
118
+ raise ValueError("duplicate ECC links")
119
+ return parsed
120
+
121
+ def read(self) -> tuple[list[dict], list[dict]]:
122
+ entries, diagnostics = [], []
123
+ roots = [root for root in (self.installation.project_memory_root, self.installation.user_memory_root) if root]
124
+ for root in roots:
125
+ for path in sorted(root.rglob("*.md"))[:1_000]:
126
+ try:
127
+ memory = self._parse(path)
128
+ is_user_root = root == self.installation.user_memory_root
129
+ allowed_scopes = {"user"} if is_user_root else {"project", "team"}
130
+ if memory["scope"] not in allowed_scopes:
131
+ raise ValueError("ECC memory scope does not match its vault location")
132
+ if memory["status"] != "active":
133
+ continue
134
+ if not self.target_harnesses.intersection(memory["targetHarnesses"]) and "all" not in memory["targetHarnesses"]:
135
+ continue
136
+ entries.append(map_ecc_memory(memory, path))
137
+ except (OSError, UnicodeError, ValueError) as error:
138
+ diagnostics.append({"path": str(path), "error": str(error)})
139
+ counts = {}
140
+ for entry in entries:
141
+ counts[entry["key"]] = counts.get(entry["key"], 0) + 1
142
+ duplicates = {key for key, count in counts.items() if count > 1}
143
+ if duplicates:
144
+ entries = [entry for entry in entries if entry["key"] not in duplicates]
145
+ diagnostics.extend({"memory_id": key, "error": "duplicate ECC memory id"} for key in sorted(duplicates))
146
+ return entries, diagnostics[:100]
147
+
148
+ def search(self, query: str, limit: int = 4) -> dict:
149
+ entries, diagnostics = self.read()
150
+ query_tokens = set(tokens(query))
151
+ ranked = []
152
+ for entry in entries:
153
+ document = set(tokens(f"{entry['title']} {entry['tags']} {entry['value']}"))
154
+ score = len(query_tokens & document) / max(len(query_tokens), 1)
155
+ if not query_tokens or score > 0:
156
+ ranked.append((score, entry))
157
+ ranked.sort(key=lambda item: (-item[0], item[1]["key"]))
158
+ return {
159
+ "entries": [{**entry, "score": score} for score, entry in ranked[:max(0, limit)]],
160
+ "diagnostics": diagnostics,
161
+ "read_only": True,
162
+ }
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def ecc_provenance(memory: dict, source_path: Path) -> dict:
7
+ return {
8
+ "adapter": "ecc",
9
+ "schema": memory["schema"],
10
+ "ecc_memory_id": memory["id"],
11
+ "source_path": str(source_path),
12
+ "source_harness": memory["sourceHarness"],
13
+ "updated_at": memory["updatedAt"],
14
+ "trust": memory["trust"],
15
+ "read_only": True,
16
+ }
@@ -0,0 +1 @@
1
+ """Public API schemas."""
@@ -0,0 +1,12 @@
1
+ from harness_context.api.v2.contracts import export_json_schemas, validate_response, validate_schema
2
+ from harness_context.api.v2.diagnostics import ContextDiagnostics
3
+ from harness_context.api.v2.enums import ContextStrategy, ErrorCode, Freshness
4
+ from harness_context.api.v2.errors import ErrorDetail, ErrorResponse, map_exception
5
+ from harness_context.api.v2.requests import PrepareContextRequest
6
+ from harness_context.api.v2.responses import ContextPackageV2
7
+
8
+ __all__ = [
9
+ "ContextDiagnostics", "ContextPackageV2", "ContextStrategy", "ErrorCode",
10
+ "ErrorDetail", "ErrorResponse", "Freshness", "PrepareContextRequest",
11
+ "export_json_schemas", "map_exception", "validate_response", "validate_schema",
12
+ ]
@@ -0,0 +1,119 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ OBJECT = {"type": "object", "additionalProperties": True}
8
+ ARRAY_OBJECT = {"type": "array", "items": OBJECT}
9
+ ARRAY_STRING = {"type": "array", "items": {"type": "string"}}
10
+ SCHEMA_VERSION = "https://json-schema.org/draft/2020-12/schema"
11
+
12
+
13
+ def _object(properties: dict[str, Any], required: tuple[str, ...] = ()) -> dict[str, Any]:
14
+ schema: dict[str, Any] = {
15
+ "$schema": SCHEMA_VERSION,
16
+ "type": "object",
17
+ "properties": properties,
18
+ "additionalProperties": False,
19
+ }
20
+ if required:
21
+ schema["required"] = list(required)
22
+ return schema
23
+
24
+
25
+ S = {"type": "string"}
26
+ I = {"type": "integer"}
27
+ N = {"type": "number"}
28
+ B = {"type": "boolean"}
29
+ NULLABLE_S = {"type": ["string", "null"]}
30
+ NULLABLE_N = {"type": ["number", "null"]}
31
+
32
+ REQUEST_SCHEMAS: dict[str, dict[str, Any]] = {
33
+ "register_workspace": _object({"workspace_id": S, "roots": ARRAY_STRING, "initial_refresh": B}, ("workspace_id", "roots")),
34
+ "refresh_workspace": _object({"workspace_id": S, "paths": {"type": ["array", "null"], "items": S}}),
35
+ "plan_context": _object({"workspace_id": S, "query": S, "available_input_tokens": I, "strategy_override": S}, ("workspace_id", "query", "available_input_tokens")),
36
+ "retrieve_context": _object({"workspace_id": S, "query": S, "top_k": I, "graph_expand": B, "token_budget": I}, ("workspace_id", "query")),
37
+ "prepare_context": _object({"workspace_id": S, "query": S, "available_input_tokens": I, "preferred_strategy": S, "include_memory": B, "include_handoff": B, "handoff_id": S, "include_ecc": B, "freshness": S, "deadline_ms": I}, ("workspace_id", "query", "available_input_tokens")),
38
+ "context_stats": _object({"workspace_id": S}),
39
+ "invalidate_context": _object({"workspace_id": S, "target": S}, ("workspace_id", "target")),
40
+ "memory_save": _object({"workspace_id": S, "key": S, "value": S, "mtype": S, "tags": S, "scope": S, "source": S, "confidence": N, "expires_at": NULLABLE_N}, ("workspace_id", "key", "value")),
41
+ "memory_search": _object({"workspace_id": S, "query": S, "mtype": S, "top_k": I, "min_sim": N}, ("workspace_id", "query")),
42
+ "memory_delete": _object({"workspace_id": S, "key": S, "mtype": S}, ("workspace_id", "key")),
43
+ "memory_list": _object({"workspace_id": S, "mtype": S, "limit": I}, ("workspace_id",)),
44
+ "handoff_conversation": _object({"workspace_id": S, "messages_json": S, "threshold_tokens": I, "label": S, "retention_seconds": I, "consent": B}, ("workspace_id", "messages_json")),
45
+ "restore_conversation_handoff": _object({"workspace_id": S, "handoff_id": S}, ("workspace_id", "handoff_id")),
46
+ "list_conversation_handoffs": _object({"workspace_id": S, "limit": I}, ("workspace_id",)),
47
+ "delete_conversation_handoff": _object({"workspace_id": S, "handoff_id": S}, ("workspace_id", "handoff_id")),
48
+ "purge_expired_handoffs": _object({}),
49
+ "ecc_status": _object({}),
50
+ "ecc_search": _object({"query": S, "limit": I}, ("query",)),
51
+ }
52
+
53
+ RESPONSE_SCHEMAS = {name: {"$schema": SCHEMA_VERSION, **OBJECT} for name in REQUEST_SCHEMAS}
54
+ RESPONSE_SCHEMAS.update({
55
+ "memory_search": {"$schema": SCHEMA_VERSION, **ARRAY_OBJECT},
56
+ "memory_list": {"$schema": SCHEMA_VERSION, **ARRAY_STRING},
57
+ "list_conversation_handoffs": {"$schema": SCHEMA_VERSION, **ARRAY_OBJECT},
58
+ "prepare_context": _object({
59
+ "api_version": S, "workspace_id": S, "snapshot_id": S, "snapshot_version": I,
60
+ "plan": OBJECT, "stable_context": {"type": ["object", "null"]},
61
+ "evidence": {"type": ["object", "null"]}, "memory": ARRAY_OBJECT,
62
+ "handoff": {"type": ["object", "null"]}, "external_context": ARRAY_OBJECT,
63
+ "diagnostics": OBJECT,
64
+ }, ("api_version", "workspace_id", "snapshot_id", "snapshot_version", "plan", "stable_context", "evidence", "memory", "handoff", "external_context", "diagnostics")),
65
+ })
66
+
67
+ ERROR_SCHEMA = _object({
68
+ "api_version": S,
69
+ "error": _object({"code": S, "message": S, "retryable": B, "details": OBJECT}, ("code", "message", "retryable", "details")),
70
+ }, ("api_version", "error"))
71
+
72
+
73
+ def _matches_type(value: Any, expected: str) -> bool:
74
+ return {"object": isinstance(value, dict), "array": isinstance(value, list), "string": isinstance(value, str), "integer": isinstance(value, int) and not isinstance(value, bool), "number": isinstance(value, (int, float)) and not isinstance(value, bool), "boolean": isinstance(value, bool), "null": value is None}[expected]
75
+
76
+
77
+ def validate_schema(value: Any, schema: dict[str, Any], path: str = "$") -> None:
78
+ expected = schema.get("type")
79
+ expected_types = expected if isinstance(expected, list) else [expected]
80
+ if expected and not any(_matches_type(value, item) for item in expected_types):
81
+ raise TypeError(f"{path} must be {expected}")
82
+ if isinstance(value, dict):
83
+ for key in schema.get("required", []):
84
+ if key not in value:
85
+ raise ValueError(f"{path}.{key} is required")
86
+ properties = schema.get("properties", {})
87
+ if schema.get("additionalProperties") is False:
88
+ unknown = set(value) - set(properties)
89
+ if unknown:
90
+ raise ValueError(f"{path} contains unknown properties: {sorted(unknown)}")
91
+ for key, item in value.items():
92
+ if key in properties:
93
+ validate_schema(item, properties[key], f"{path}.{key}")
94
+ if isinstance(value, list) and "items" in schema:
95
+ for index, item in enumerate(value):
96
+ validate_schema(item, schema["items"], f"{path}[{index}]")
97
+
98
+
99
+ def validate_response(contract: str, value: Any) -> Any:
100
+ try:
101
+ schema = RESPONSE_SCHEMAS[contract]
102
+ except KeyError as error:
103
+ raise KeyError(f"unknown MCP v2 response contract: {contract}") from error
104
+ validate_schema(value, schema)
105
+ return value
106
+
107
+
108
+ def export_json_schemas(directory: str | Path) -> list[Path]:
109
+ destination = Path(directory)
110
+ destination.mkdir(parents=True, exist_ok=True)
111
+ documents = {f"{name}.request.schema.json": schema for name, schema in REQUEST_SCHEMAS.items()}
112
+ documents.update({f"{name}.response.schema.json": schema for name, schema in RESPONSE_SCHEMAS.items()})
113
+ documents["error.response.schema.json"] = ERROR_SCHEMA
114
+ written = []
115
+ for filename, schema in sorted(documents.items()):
116
+ path = destination / filename
117
+ path.write_text(json.dumps(schema, indent=2, sort_keys=True) + "\n", encoding="utf-8")
118
+ written.append(path)
119
+ return written
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class ContextDiagnostics:
9
+ coverage: str = "not_applicable"
10
+ freshness: str = "current"
11
+ untrusted_content: bool = True
12
+ latency_ms: float = 0.0
13
+ ecc: dict[str, Any] = field(default_factory=dict)
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class ContextStrategy(str, Enum):
7
+ AUTO = "auto"
8
+
9
+
10
+ class Freshness(str, Enum):
11
+ CURRENT = "current"
12
+
13
+
14
+ class ErrorCode(str, Enum):
15
+ INTERNAL_ERROR = "internal_error"
16
+ INVALID_REQUEST = "invalid_request"
17
+ NOT_FOUND = "not_found"
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict, dataclass, field
4
+ from typing import Any
5
+
6
+ from harness_context.api.v2.enums import ErrorCode
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ErrorDetail:
11
+ code: str
12
+ message: str
13
+ retryable: bool = False
14
+ details: dict[str, Any] = field(default_factory=dict)
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class ErrorResponse:
19
+ error: ErrorDetail
20
+ api_version: str = "2.0"
21
+
22
+ def to_dict(self) -> dict[str, Any]:
23
+ return asdict(self)
24
+
25
+
26
+ def map_exception(error: Exception) -> ErrorResponse:
27
+ code = getattr(error, "code", None)
28
+ if isinstance(code, str):
29
+ return ErrorResponse(ErrorDetail(code=code, message=str(error)))
30
+ if isinstance(error, (TypeError, ValueError)):
31
+ return ErrorResponse(ErrorDetail(code=ErrorCode.INVALID_REQUEST.value, message=str(error)))
32
+ return ErrorResponse(ErrorDetail(code=ErrorCode.INTERNAL_ERROR.value, message=str(error)))
@@ -0,0 +1,4 @@
1
+ from harness_context.api.v2.requests import PrepareContextRequest
2
+ from harness_context.api.v2.responses import ContextPackageV2
3
+
4
+ __all__ = ["ContextPackageV2", "PrepareContextRequest"]
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class PrepareContextRequest:
8
+ workspace_id: str
9
+ query: str
10
+ available_input_tokens: int
11
+ preferred_strategy: str = "auto"
12
+ include_memory: bool = True
13
+ include_handoff: bool = False
14
+ handoff_id: str = ""
15
+ include_ecc: bool = False
16
+ freshness: str = "current"
17
+ deadline_ms: int = 5_000
@@ -0,0 +1,22 @@
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 ContextPackageV2:
9
+ api_version: str
10
+ workspace_id: str
11
+ snapshot_id: str
12
+ snapshot_version: int
13
+ plan: dict[str, Any]
14
+ stable_context: dict[str, Any] | None
15
+ evidence: dict[str, Any] | None
16
+ memory: list[dict[str, Any]] = field(default_factory=list)
17
+ handoff: dict[str, Any] | None = None
18
+ external_context: list[dict[str, Any]] = field(default_factory=list)
19
+ diagnostics: dict[str, Any] = field(default_factory=dict)
20
+
21
+ def to_dict(self) -> dict[str, Any]:
22
+ return asdict(self)
@@ -0,0 +1,3 @@
1
+ from harness_context.application.services import ContextService, RefreshService
2
+
3
+ __all__ = ["ContextService", "RefreshService"]
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from compact.handoff import ConversationHandoffStore
6
+ from harness_context.adapters.ecc import EccMemoryReader
7
+ from harness_context.application.ecc_service import EccService
8
+ from harness_context.application.handoff_service import HandoffService
9
+ from harness_context.application.memory_service import MemoryService
10
+ from harness_context.application.retrieval_service import RetrievalService
11
+ from harness_context.application.services import ContextService, RefreshService
12
+ from harness_context.application.workspace_service import WorkspaceService
13
+ from harness_context.engine import ContextEngine
14
+ from harness_context.storage import SnapshotStore
15
+ from memory.episodic import MemoryStore
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ApplicationContainer:
20
+ engine: ContextEngine
21
+ snapshots: SnapshotStore
22
+ refresh: RefreshService
23
+ context: ContextService
24
+ memory: MemoryStore
25
+ handoffs: ConversationHandoffStore
26
+ ecc: EccMemoryReader | None
27
+ workspace: WorkspaceService
28
+ retrieval: RetrievalService
29
+ memories: MemoryService
30
+ handoff: HandoffService
31
+ ecc_queries: EccService