kc-cli 0.4.0__py3-none-any.whl
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.
- kc/__init__.py +5 -0
- kc/__main__.py +11 -0
- kc/artifacts/__init__.py +1 -0
- kc/artifacts/diff.py +76 -0
- kc/artifacts/frontmatter.py +26 -0
- kc/artifacts/markdown.py +116 -0
- kc/atomic_write.py +33 -0
- kc/cli.py +284 -0
- kc/commands/__init__.py +1 -0
- kc/commands/artifact.py +1190 -0
- kc/commands/citation.py +231 -0
- kc/commands/common.py +346 -0
- kc/commands/conformance.py +293 -0
- kc/commands/context.py +190 -0
- kc/commands/doctor.py +81 -0
- kc/commands/eval.py +133 -0
- kc/commands/export.py +97 -0
- kc/commands/guide.py +571 -0
- kc/commands/index.py +54 -0
- kc/commands/init.py +207 -0
- kc/commands/lint.py +238 -0
- kc/commands/source.py +464 -0
- kc/commands/status.py +52 -0
- kc/commands/task.py +260 -0
- kc/config.py +127 -0
- kc/embedding_models/potion-base-8M/README.md +97 -0
- kc/embedding_models/potion-base-8M/config.json +13 -0
- kc/embedding_models/potion-base-8M/model.safetensors +0 -0
- kc/embedding_models/potion-base-8M/modules.json +14 -0
- kc/embedding_models/potion-base-8M/tokenizer.json +1 -0
- kc/errors.py +141 -0
- kc/fingerprints.py +35 -0
- kc/ids.py +23 -0
- kc/locks.py +65 -0
- kc/models/__init__.py +17 -0
- kc/models/artifact.py +34 -0
- kc/models/citation.py +60 -0
- kc/models/context.py +23 -0
- kc/models/eval.py +21 -0
- kc/models/plan.py +37 -0
- kc/models/source.py +37 -0
- kc/models/source_range.py +29 -0
- kc/models/source_revision.py +19 -0
- kc/models/task.py +35 -0
- kc/output.py +838 -0
- kc/paths.py +126 -0
- kc/provenance/__init__.py +1 -0
- kc/provenance/citations.py +296 -0
- kc/search/__init__.py +1 -0
- kc/search/extract.py +268 -0
- kc/search/fts.py +284 -0
- kc/search/semantic.py +346 -0
- kc/store/__init__.py +1 -0
- kc/store/jsonl.py +55 -0
- kc/store/sqlite.py +444 -0
- kc/store/transaction.py +67 -0
- kc/templates/agents/skills/kc/SKILL.md +282 -0
- kc/templates/agents/skills/kc/agents/openai.yaml +5 -0
- kc/templates/agents/skills/kc/scripts/resolve_query_citations.py +134 -0
- kc/workspace.py +98 -0
- kc_cli-0.4.0.dist-info/METADATA +522 -0
- kc_cli-0.4.0.dist-info/RECORD +65 -0
- kc_cli-0.4.0.dist-info/WHEEL +4 -0
- kc_cli-0.4.0.dist-info/entry_points.txt +2 -0
- kc_cli-0.4.0.dist-info/licenses/LICENSE +21 -0
kc/errors.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Typed errors and exit-code contract for kc."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
EXIT_OK = 0
|
|
9
|
+
EXIT_USAGE = 2
|
|
10
|
+
EXIT_VALIDATION = 10
|
|
11
|
+
EXIT_NOT_FOUND = 11
|
|
12
|
+
EXIT_ALREADY_EXISTS = 12
|
|
13
|
+
EXIT_CONFLICT = 13
|
|
14
|
+
EXIT_PROVENANCE = 20
|
|
15
|
+
EXIT_INDEX = 30
|
|
16
|
+
EXIT_RETRIEVAL_MODEL = 31
|
|
17
|
+
EXIT_WAITING = 40
|
|
18
|
+
EXIT_IO = 50
|
|
19
|
+
EXIT_LOCK = 60
|
|
20
|
+
EXIT_PERSISTENCE = 70
|
|
21
|
+
EXIT_UNSUPPORTED = 80
|
|
22
|
+
EXIT_INTERNAL = 90
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
ERROR_EXIT_MAP: dict[str, int] = {
|
|
26
|
+
"KC_USAGE_ERROR": EXIT_USAGE,
|
|
27
|
+
"KC_CONFIG_NOT_FOUND": EXIT_NOT_FOUND,
|
|
28
|
+
"KC_CONFIG_INVALID": EXIT_VALIDATION,
|
|
29
|
+
"KC_SOURCE_NOT_FOUND": EXIT_NOT_FOUND,
|
|
30
|
+
"KC_SOURCE_ALREADY_REGISTERED": EXIT_ALREADY_EXISTS,
|
|
31
|
+
"KC_SOURCE_STALE": EXIT_PROVENANCE,
|
|
32
|
+
"KC_SOURCE_UNSUPPORTED_MEDIA_TYPE": EXIT_UNSUPPORTED,
|
|
33
|
+
"KC_RANGE_NOT_FOUND": EXIT_NOT_FOUND,
|
|
34
|
+
"KC_ARTIFACT_NOT_FOUND": EXIT_NOT_FOUND,
|
|
35
|
+
"KC_ARTIFACT_SCHEMA_INVALID": EXIT_VALIDATION,
|
|
36
|
+
"KC_ARTIFACT_STATUS_INVALID": EXIT_VALIDATION,
|
|
37
|
+
"KC_CITATION_INVALID_TOKEN": EXIT_PROVENANCE,
|
|
38
|
+
"KC_CITATION_SOURCE_MISSING": EXIT_PROVENANCE,
|
|
39
|
+
"KC_CITATION_RANGE_MISSING": EXIT_PROVENANCE,
|
|
40
|
+
"KC_CITATION_STALE_SOURCE": EXIT_PROVENANCE,
|
|
41
|
+
"KC_VALIDATION_MISSING_CITATION": EXIT_VALIDATION,
|
|
42
|
+
"KC_VALIDATION_TODO_IN_ACTIVE_ARTIFACT": EXIT_VALIDATION,
|
|
43
|
+
"KC_VALIDATION_INVALID_ARGUMENT": EXIT_VALIDATION,
|
|
44
|
+
"KC_PLAN_PRECONDITION_FAILED": EXIT_CONFLICT,
|
|
45
|
+
"KC_CONFORMANCE_FAILED": EXIT_VALIDATION,
|
|
46
|
+
"KC_APPLY_REQUIRES_YES": EXIT_VALIDATION,
|
|
47
|
+
"KC_APPLY_NOT_VALIDATED": EXIT_VALIDATION,
|
|
48
|
+
"KC_LOCK_HELD": EXIT_LOCK,
|
|
49
|
+
"KC_INDEX_BUILD_FAILED": EXIT_INDEX,
|
|
50
|
+
"KC_RETRIEVAL_MODEL_UNAVAILABLE": EXIT_RETRIEVAL_MODEL,
|
|
51
|
+
"KC_UNSUPPORTED_FEATURE": EXIT_UNSUPPORTED,
|
|
52
|
+
"KC_PATH_OUTSIDE_REPO": EXIT_VALIDATION,
|
|
53
|
+
"KC_FILE_NOT_FOUND": EXIT_NOT_FOUND,
|
|
54
|
+
"KC_FILE_EXISTS": EXIT_ALREADY_EXISTS,
|
|
55
|
+
"KC_JSON_INVALID": EXIT_VALIDATION,
|
|
56
|
+
"KC_TASK_NOT_FOUND": EXIT_NOT_FOUND,
|
|
57
|
+
"KC_TASK_NOT_WAITING": EXIT_CONFLICT,
|
|
58
|
+
"KC_EVENT_INVALID": EXIT_VALIDATION,
|
|
59
|
+
"KC_INTERNAL_ERROR": EXIT_INTERNAL,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
ERROR_CATEGORIES: dict[str, str] = {
|
|
64
|
+
"CONFIG": "configuration",
|
|
65
|
+
"USAGE": "usage",
|
|
66
|
+
"SOURCE": "source",
|
|
67
|
+
"RANGE": "source_range",
|
|
68
|
+
"ARTIFACT": "artifact",
|
|
69
|
+
"CITATION": "provenance",
|
|
70
|
+
"VALIDATION": "validation",
|
|
71
|
+
"PLAN": "plan",
|
|
72
|
+
"CONFORMANCE": "validation",
|
|
73
|
+
"APPLY": "apply",
|
|
74
|
+
"LOCK": "concurrency",
|
|
75
|
+
"INDEX": "index",
|
|
76
|
+
"RETRIEVAL": "retrieval",
|
|
77
|
+
"PATH": "validation",
|
|
78
|
+
"FILE": "io",
|
|
79
|
+
"JSON": "validation",
|
|
80
|
+
"TASK": "task",
|
|
81
|
+
"EVENT": "task",
|
|
82
|
+
"UNSUPPORTED": "unsupported",
|
|
83
|
+
"INTERNAL": "internal",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def exit_code_for(code: str) -> int:
|
|
88
|
+
return ERROR_EXIT_MAP.get(code, EXIT_INTERNAL)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def category_for(code: str) -> str:
|
|
92
|
+
parts = code.split("_")
|
|
93
|
+
if len(parts) >= 2:
|
|
94
|
+
return ERROR_CATEGORIES.get(parts[1], "internal")
|
|
95
|
+
return "internal"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class KcError(Exception):
|
|
100
|
+
"""Domain error surfaced through the kc.result.v1 envelope."""
|
|
101
|
+
|
|
102
|
+
code: str
|
|
103
|
+
message: str
|
|
104
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
105
|
+
category: str | None = None
|
|
106
|
+
exit_code: int | None = None
|
|
107
|
+
retryable: bool = False
|
|
108
|
+
suggested_action: str | None = None
|
|
109
|
+
|
|
110
|
+
def __post_init__(self) -> None:
|
|
111
|
+
super().__init__(self.message)
|
|
112
|
+
if self.category is None:
|
|
113
|
+
self.category = category_for(self.code)
|
|
114
|
+
if self.exit_code is None:
|
|
115
|
+
self.exit_code = exit_code_for(self.code)
|
|
116
|
+
if self.suggested_action is None:
|
|
117
|
+
if self.retryable:
|
|
118
|
+
self.suggested_action = "retry"
|
|
119
|
+
elif self.exit_code in {EXIT_USAGE, EXIT_VALIDATION, EXIT_NOT_FOUND, EXIT_ALREADY_EXISTS}:
|
|
120
|
+
self.suggested_action = "fix_input"
|
|
121
|
+
elif self.exit_code == EXIT_LOCK:
|
|
122
|
+
self.suggested_action = "inspect lock with kc doctor locks or retry later"
|
|
123
|
+
else:
|
|
124
|
+
self.suggested_action = "escalate"
|
|
125
|
+
|
|
126
|
+
def to_message(self) -> dict[str, Any]:
|
|
127
|
+
return {
|
|
128
|
+
"code": self.code,
|
|
129
|
+
"category": self.category,
|
|
130
|
+
"message": self.message,
|
|
131
|
+
"exit_code": self.exit_code,
|
|
132
|
+
"retryable": self.retryable,
|
|
133
|
+
"suggested_action": self.suggested_action,
|
|
134
|
+
"details": self.details,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def validation_error(
|
|
139
|
+
message: str, *, code: str = "KC_ARTIFACT_SCHEMA_INVALID", **details: Any
|
|
140
|
+
) -> KcError:
|
|
141
|
+
return KcError(code=code, message=message, details=details)
|
kc/fingerprints.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""File and text fingerprint helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sha256_bytes(data: bytes) -> str:
|
|
10
|
+
return f"sha256:{hashlib.sha256(data).hexdigest()}"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def normalize_text(text: str) -> str:
|
|
14
|
+
return text.replace("\r\n", "\n").replace("\r", "\n")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def raw_fingerprint(path: Path) -> str:
|
|
18
|
+
h = hashlib.sha256()
|
|
19
|
+
with path.open("rb") as f:
|
|
20
|
+
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
|
21
|
+
h.update(chunk)
|
|
22
|
+
return f"sha256:{h.hexdigest()}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalized_fingerprint(path: Path) -> str:
|
|
26
|
+
text = normalize_text(path.read_text(encoding="utf-8-sig"))
|
|
27
|
+
return sha256_bytes(text.encode("utf-8"))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def text_hash(text: str) -> str:
|
|
31
|
+
return sha256_bytes(normalize_text(text).encode("utf-8"))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def fingerprint_text(text: str) -> str:
|
|
35
|
+
return text_hash(text)
|
kc/ids.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Deterministic-looking public IDs with kc prefixes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from ulid import ULID
|
|
10
|
+
except Exception: # pragma: no cover - fallback for unusual environments
|
|
11
|
+
ULID = None # type: ignore[assignment]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def new_id(prefix: str) -> str:
|
|
15
|
+
if ULID is not None:
|
|
16
|
+
return f"{prefix}_{ULID()}"
|
|
17
|
+
return f"{prefix}_{uuid.uuid4().hex}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def stable_id(prefix: str, *parts: str) -> str:
|
|
21
|
+
material = "\x1f".join(parts)
|
|
22
|
+
digest = hashlib.sha256(material.encode("utf-8")).hexdigest()[:26].upper()
|
|
23
|
+
return f"{prefix}_{digest}"
|
kc/locks.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Simple visible lock files for write commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import socket
|
|
8
|
+
from contextlib import suppress
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from kc.errors import KcError
|
|
15
|
+
from kc.ids import new_id
|
|
16
|
+
from kc.output import state
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class FileLock:
|
|
21
|
+
locks_dir: Path
|
|
22
|
+
name: str
|
|
23
|
+
command: str
|
|
24
|
+
target: str
|
|
25
|
+
|
|
26
|
+
def __post_init__(self) -> None:
|
|
27
|
+
self.locks_dir.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
self.path = self.locks_dir / f"{self.name}.lock"
|
|
29
|
+
self.acquired = False
|
|
30
|
+
|
|
31
|
+
def __enter__(self) -> FileLock:
|
|
32
|
+
metadata = {
|
|
33
|
+
"schema_version": "kc.lock.v1",
|
|
34
|
+
"lock_id": new_id("lock"),
|
|
35
|
+
"created_at": datetime.now(UTC).isoformat(),
|
|
36
|
+
"pid": os.getpid(),
|
|
37
|
+
"hostname": socket.gethostname(),
|
|
38
|
+
"command": self.command,
|
|
39
|
+
"request_id": state.request_id,
|
|
40
|
+
"target": self.target,
|
|
41
|
+
}
|
|
42
|
+
try:
|
|
43
|
+
fd = os.open(str(self.path), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
44
|
+
except FileExistsError as exc:
|
|
45
|
+
holder: dict[str, Any] = {}
|
|
46
|
+
try:
|
|
47
|
+
holder = json.loads(self.path.read_text(encoding="utf-8"))
|
|
48
|
+
except Exception:
|
|
49
|
+
holder = {"lock_file": str(self.path)}
|
|
50
|
+
raise KcError(
|
|
51
|
+
code="KC_LOCK_HELD",
|
|
52
|
+
message=f"Lock is held: {self.path}",
|
|
53
|
+
details={"lock_file": str(self.path), "holder": holder},
|
|
54
|
+
retryable=True,
|
|
55
|
+
) from exc
|
|
56
|
+
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
57
|
+
json.dump(metadata, f, indent=2)
|
|
58
|
+
f.write("\n")
|
|
59
|
+
self.acquired = True
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
|
|
63
|
+
if self.acquired:
|
|
64
|
+
with suppress(FileNotFoundError):
|
|
65
|
+
self.path.unlink()
|
kc/models/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from kc.models.artifact import ArtifactRecord
|
|
2
|
+
from kc.models.citation import CitationEdgeRecord
|
|
3
|
+
from kc.models.plan import PlanRecord
|
|
4
|
+
from kc.models.source import Authority, SourceRecord
|
|
5
|
+
from kc.models.source_range import Locator, SourceRangeRecord
|
|
6
|
+
from kc.models.task import TaskRecord
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ArtifactRecord",
|
|
10
|
+
"Authority",
|
|
11
|
+
"CitationEdgeRecord",
|
|
12
|
+
"Locator",
|
|
13
|
+
"PlanRecord",
|
|
14
|
+
"SourceRangeRecord",
|
|
15
|
+
"SourceRecord",
|
|
16
|
+
"TaskRecord",
|
|
17
|
+
]
|
kc/models/artifact.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SourceRef(BaseModel):
|
|
9
|
+
source_id: str
|
|
10
|
+
range_ids: list[str] = Field(default_factory=list)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ArtifactRecord(BaseModel):
|
|
14
|
+
schema_version: Literal["kc.artifact.v1"] = "kc.artifact.v1"
|
|
15
|
+
artifact_id: str
|
|
16
|
+
path: str
|
|
17
|
+
artifact_type: Literal[
|
|
18
|
+
"knowledge_page",
|
|
19
|
+
"glossary",
|
|
20
|
+
"decision_note",
|
|
21
|
+
"source_index",
|
|
22
|
+
"log_entry",
|
|
23
|
+
"eval_pack",
|
|
24
|
+
] = "knowledge_page"
|
|
25
|
+
title: str
|
|
26
|
+
status: Literal["draft", "active", "deprecated", "superseded"] = "draft"
|
|
27
|
+
domain: list[str] = Field(default_factory=list)
|
|
28
|
+
fingerprint: str
|
|
29
|
+
created_at: str
|
|
30
|
+
updated_at: str
|
|
31
|
+
last_validated_at: str | None = None
|
|
32
|
+
validation_status: Literal["passed", "failed", "unknown"] = "unknown"
|
|
33
|
+
source_refs: list[SourceRef] = Field(default_factory=list)
|
|
34
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
kc/models/citation.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from kc.models.source_range import Locator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ArtifactLocator(BaseModel):
|
|
11
|
+
kind: Literal["line_range"] = "line_range"
|
|
12
|
+
start_line: int
|
|
13
|
+
end_line: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CitationEdgeRecord(BaseModel):
|
|
17
|
+
schema_version: Literal["kc.citation_edge.v1"] = "kc.citation_edge.v1"
|
|
18
|
+
edge_id: str
|
|
19
|
+
artifact_id: str | None = None
|
|
20
|
+
artifact_path: str
|
|
21
|
+
artifact_locator: ArtifactLocator
|
|
22
|
+
citation_token: str
|
|
23
|
+
source_id: str
|
|
24
|
+
range_id: str | None = None
|
|
25
|
+
source_fingerprint_at_validation: str | None = None
|
|
26
|
+
validated_at: str
|
|
27
|
+
status: Literal[
|
|
28
|
+
"valid",
|
|
29
|
+
"missing_source",
|
|
30
|
+
"missing_range",
|
|
31
|
+
"stale_source",
|
|
32
|
+
"locator_mismatch",
|
|
33
|
+
"invalid_token",
|
|
34
|
+
] = "valid"
|
|
35
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ParsedCitation(BaseModel):
|
|
39
|
+
token: str
|
|
40
|
+
source_id: str
|
|
41
|
+
range_id: str | None = None
|
|
42
|
+
token_version: Literal["v1", "v2"] = "v1"
|
|
43
|
+
kind: Literal["line_range", "json_pointer", "csv_row_range"]
|
|
44
|
+
line: int
|
|
45
|
+
start_line: int | None = None
|
|
46
|
+
end_line: int | None = None
|
|
47
|
+
pointer: str | None = None
|
|
48
|
+
start_row: int | None = None
|
|
49
|
+
end_row: int | None = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def locator(self) -> Locator:
|
|
53
|
+
return Locator(
|
|
54
|
+
kind=self.kind,
|
|
55
|
+
start_line=self.start_line,
|
|
56
|
+
end_line=self.end_line,
|
|
57
|
+
pointer=self.pointer,
|
|
58
|
+
start_row=self.start_row,
|
|
59
|
+
end_row=self.end_row,
|
|
60
|
+
)
|
kc/models/context.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContextPackRecord(BaseModel):
|
|
9
|
+
schema_version: Literal["kc.context_pack.v1"] = "kc.context_pack.v1"
|
|
10
|
+
context_id: str
|
|
11
|
+
created_at: str
|
|
12
|
+
ask: str
|
|
13
|
+
shape: str = "knowledge_page"
|
|
14
|
+
target: str | None = None
|
|
15
|
+
grounding_policy: str = "required"
|
|
16
|
+
workspace: dict[str, Any] = Field(default_factory=dict)
|
|
17
|
+
candidate_ranges: list[dict[str, Any]] = Field(default_factory=list)
|
|
18
|
+
existing_artifacts: list[dict[str, Any]] = Field(default_factory=list)
|
|
19
|
+
citation_policy: dict[str, Any] = Field(default_factory=dict)
|
|
20
|
+
artifact_policy: dict[str, Any] = Field(default_factory=dict)
|
|
21
|
+
agent_instructions: list[str] = Field(default_factory=list)
|
|
22
|
+
next_commands: list[str] = Field(default_factory=list)
|
|
23
|
+
validation: dict[str, Any] = Field(default_factory=dict)
|
kc/models/eval.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EvalCase(BaseModel):
|
|
9
|
+
id: str
|
|
10
|
+
query: str
|
|
11
|
+
domain: str | None = None
|
|
12
|
+
limit: int = 10
|
|
13
|
+
expected_source_ids: list[str] = Field(default_factory=list)
|
|
14
|
+
expected_range_ids: list[str] = Field(default_factory=list)
|
|
15
|
+
must_include_citation_tokens: list[str] = Field(default_factory=list)
|
|
16
|
+
min_recall_at_k: float = 1.0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EvalPack(BaseModel):
|
|
20
|
+
schema_version: Literal["kc.eval_pack.v1"] = "kc.eval_pack.v1"
|
|
21
|
+
cases: list[EvalCase] = Field(default_factory=list)
|
kc/models/plan.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PlanOperation(BaseModel):
|
|
9
|
+
op_id: str
|
|
10
|
+
kind: str
|
|
11
|
+
path: str
|
|
12
|
+
before_fingerprint: str | None = None
|
|
13
|
+
after_fingerprint: str | None = None
|
|
14
|
+
risk: Literal["low", "medium", "high"] = "medium"
|
|
15
|
+
diff_path: str | None = None
|
|
16
|
+
requires_yes: bool = True
|
|
17
|
+
details: dict[str, Any] = Field(default_factory=dict)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PlanCondition(BaseModel):
|
|
21
|
+
kind: str
|
|
22
|
+
path: str | None = None
|
|
23
|
+
expected: str | None = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PlanRecord(BaseModel):
|
|
27
|
+
schema_version: Literal["kc.plan.v1"] = "kc.plan.v1"
|
|
28
|
+
plan_id: str
|
|
29
|
+
created_at: str
|
|
30
|
+
command: str
|
|
31
|
+
mode: Literal["dry_run", "apply"] = "dry_run"
|
|
32
|
+
idempotency_key: str | None = None
|
|
33
|
+
operations: list[PlanOperation] = Field(default_factory=list)
|
|
34
|
+
preconditions: list[PlanCondition] = Field(default_factory=list)
|
|
35
|
+
postconditions: list[PlanCondition] = Field(default_factory=list)
|
|
36
|
+
risk_flags: list[str] = Field(default_factory=list)
|
|
37
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
kc/models/source.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Authority(BaseModel):
|
|
9
|
+
level: Literal["unknown", "informal", "team-approved", "enterprise-approved", "regulatory"] = (
|
|
10
|
+
"unknown"
|
|
11
|
+
)
|
|
12
|
+
owner: str | None = None
|
|
13
|
+
review_date: str | None = None
|
|
14
|
+
notes: str = "Do not infer authority from file location."
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SourceRecord(BaseModel):
|
|
18
|
+
schema_version: Literal["kc.source.v1"] = "kc.source.v1"
|
|
19
|
+
source_id: str
|
|
20
|
+
uri: str
|
|
21
|
+
display_name: str
|
|
22
|
+
media_type: str = "text/plain"
|
|
23
|
+
fingerprint: str
|
|
24
|
+
raw_fingerprint: str | None = None
|
|
25
|
+
normalized_fingerprint: str | None = None
|
|
26
|
+
fingerprint_algorithm: str = "sha256-normalized-v1"
|
|
27
|
+
registered_at: str
|
|
28
|
+
registered_by: str = "agent-or-human"
|
|
29
|
+
status: Literal["active", "stale", "superseded", "missing", "excluded"] = "active"
|
|
30
|
+
immutability: Literal["fingerprinted", "external", "copied"] = "fingerprinted"
|
|
31
|
+
domain: list[str] = Field(default_factory=list)
|
|
32
|
+
authority: Authority = Field(default_factory=Authority)
|
|
33
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
34
|
+
canonical_source_key: str | None = None
|
|
35
|
+
current_revision_id: str | None = None
|
|
36
|
+
first_registered_at: str | None = None
|
|
37
|
+
last_refreshed_at: str | None = None
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Locator(BaseModel):
|
|
9
|
+
kind: Literal["line_range", "json_pointer", "csv_row_range", "page_text_range"] = "line_range"
|
|
10
|
+
start_line: int | None = None
|
|
11
|
+
end_line: int | None = None
|
|
12
|
+
pointer: str | None = None
|
|
13
|
+
start_row: int | None = None
|
|
14
|
+
end_row: int | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SourceRangeRecord(BaseModel):
|
|
18
|
+
schema_version: Literal["kc.source_range.v1"] = "kc.source_range.v1"
|
|
19
|
+
range_id: str
|
|
20
|
+
source_id: str
|
|
21
|
+
revision_id: str | None = None
|
|
22
|
+
source_fingerprint: str
|
|
23
|
+
locator: Locator
|
|
24
|
+
text_hash: str
|
|
25
|
+
excerpt: str
|
|
26
|
+
tokens_estimate: int = 0
|
|
27
|
+
extracted_at: str
|
|
28
|
+
status: Literal["active", "superseded"] = "active"
|
|
29
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SourceRevisionRecord(BaseModel):
|
|
9
|
+
schema_version: Literal["kc.source_revision.v1"] = "kc.source_revision.v1"
|
|
10
|
+
revision_id: str
|
|
11
|
+
source_id: str
|
|
12
|
+
uri: str
|
|
13
|
+
raw_fingerprint: str
|
|
14
|
+
normalized_fingerprint: str
|
|
15
|
+
media_type: str
|
|
16
|
+
extracted_at: str
|
|
17
|
+
status: Literal["active", "superseded"] = "active"
|
|
18
|
+
previous_revision_id: str | None = None
|
|
19
|
+
metadata: dict[str, Any] = Field(default_factory=dict)
|
kc/models/task.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TaskRecord(BaseModel):
|
|
9
|
+
schema_version: Literal["kc.task.v1"] = "kc.task.v1"
|
|
10
|
+
task_id: str
|
|
11
|
+
goal: str
|
|
12
|
+
status: Literal[
|
|
13
|
+
"created",
|
|
14
|
+
"awaiting_agent",
|
|
15
|
+
"awaiting_validation",
|
|
16
|
+
"awaiting_apply",
|
|
17
|
+
"completed",
|
|
18
|
+
"blocked",
|
|
19
|
+
"cancelled",
|
|
20
|
+
"failed",
|
|
21
|
+
] = "awaiting_agent"
|
|
22
|
+
context_pack_id: str | None = None
|
|
23
|
+
context_pack_path: str | None = None
|
|
24
|
+
created_at: str
|
|
25
|
+
updated_at: str
|
|
26
|
+
shape: str = "knowledge_page"
|
|
27
|
+
domain: list[str] = Field(default_factory=list)
|
|
28
|
+
candidate_sources: list[str] = Field(default_factory=list)
|
|
29
|
+
candidate_ranges: list[str] = Field(default_factory=list)
|
|
30
|
+
target_artifacts: list[str] = Field(default_factory=list)
|
|
31
|
+
agent_instructions: list[str] = Field(default_factory=list)
|
|
32
|
+
next_commands: list[str] = Field(default_factory=list)
|
|
33
|
+
expected_event_name: str | None = None
|
|
34
|
+
expected_event_schema: dict[str, Any] | None = None
|
|
35
|
+
events: list[dict[str, Any]] = Field(default_factory=list)
|