knowcode 0.1.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.
- knowcode-0.1.0.dist-info/METADATA +175 -0
- knowcode-0.1.0.dist-info/RECORD +63 -0
- knowcode-0.1.0.dist-info/WHEEL +4 -0
- knowcode-0.1.0.dist-info/entry_points.txt +2 -0
- runtime/__init__.py +4 -0
- runtime/artifact/__init__.py +1 -0
- runtime/artifact/builder.py +179 -0
- runtime/cli/__init__.py +1 -0
- runtime/cli/animation.py +278 -0
- runtime/cli/app.py +309 -0
- runtime/cli/auth.py +171 -0
- runtime/cli/telemetry.py +91 -0
- runtime/exceptions/__init__.py +1 -0
- runtime/exceptions/errors.py +99 -0
- runtime/repository/__init__.py +13 -0
- runtime/repository/discovery.py +64 -0
- runtime/repository/models.py +103 -0
- runtime/repository/paths.py +50 -0
- runtime/repository/validator.py +100 -0
- runtime/services/__init__.py +1 -0
- runtime/services/ingest_service.py +105 -0
- runtime/services/init_service.py +45 -0
- runtime/services/semantic_sync_service.py +55 -0
- runtime/services/status_service.py +40 -0
- runtime/services/sync_service.py +57 -0
- runtime/templates/KNOWCODE_LOADER.md.j2 +24 -0
- runtime/templates/README_KNOWLEDGE.md.j2 +12 -0
- runtime/templates/README_STRUCTURE.md.j2 +19 -0
- runtime/templates/__init__.py +1 -0
- runtime/templates/active_context.md.j2 +3 -0
- runtime/templates/ingest_legacy.md.j2 +15 -0
- runtime/templates/raw_readme.md.j2 +9 -0
- runtime/templates/sync_reconciliation.md.j2 +17 -0
- runtime/templates/synthesize_knowledge.md.j2 +32 -0
- runtime/templates/track_intent.md.j2 +14 -0
- structural_engine/__init__.py +3 -0
- structural_engine/diff/__init__.py +1 -0
- structural_engine/diff/generator.py +92 -0
- structural_engine/diff/models.py +48 -0
- structural_engine/engine.py +192 -0
- structural_engine/logs/__init__.py +1 -0
- structural_engine/logs/generator.py +33 -0
- structural_engine/parser/__init__.py +7 -0
- structural_engine/parser/discovery.py +165 -0
- structural_engine/parser/extractors/base.py +44 -0
- structural_engine/parser/languages/javascript/adapter.py +149 -0
- structural_engine/parser/languages/python/adapter.py +174 -0
- structural_engine/parser/languages/typescript/adapter.py +165 -0
- structural_engine/parser/models.py +186 -0
- structural_engine/parser/parser.py +160 -0
- structural_engine/parser/resolvers/calls.py +105 -0
- structural_engine/parser/tree_sitter/registry.py +61 -0
- structural_engine/reports/__init__.py +1 -0
- structural_engine/reports/generator.py +77 -0
- structural_engine/results.py +54 -0
- structural_engine/revisions/__init__.py +1 -0
- structural_engine/revisions/tracker.py +32 -0
- structural_engine/snapshot/__init__.py +1 -0
- structural_engine/snapshot/generator.py +58 -0
- structural_engine/snapshot/loader.py +59 -0
- structural_engine/state/__init__.py +1 -0
- structural_engine/state/manager.py +169 -0
- structural_engine/state/models.py +34 -0
runtime/cli/auth.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Authentication and configuration module for KnowCode CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import urllib.request
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import urllib.error
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from platformdirs import user_config_dir
|
|
13
|
+
import typer
|
|
14
|
+
import questionary
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from runtime.exceptions.errors import KnowcodeError
|
|
17
|
+
|
|
18
|
+
CONFIG_DIR = Path(user_config_dir("knowcode"))
|
|
19
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def save_access_key(key: str) -> None:
|
|
23
|
+
"""Save the beta tester access key to the user config file."""
|
|
24
|
+
try:
|
|
25
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
config_data = {}
|
|
27
|
+
if CONFIG_FILE.exists():
|
|
28
|
+
try:
|
|
29
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
30
|
+
config_data = json.load(f)
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
config_data["access_key"] = key.strip()
|
|
35
|
+
|
|
36
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
37
|
+
json.dump(config_data, f, indent=4)
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def clear_access_key() -> None:
|
|
43
|
+
"""Clear the stored access key from the user config file."""
|
|
44
|
+
try:
|
|
45
|
+
if CONFIG_FILE.exists():
|
|
46
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
47
|
+
config_data = json.load(f)
|
|
48
|
+
if "access_key" in config_data:
|
|
49
|
+
del config_data["access_key"]
|
|
50
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
51
|
+
json.dump(config_data, f, indent=4)
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_access_key() -> str | None:
|
|
57
|
+
"""Retrieve the stored access key, or None if not authenticated."""
|
|
58
|
+
try:
|
|
59
|
+
if CONFIG_FILE.exists():
|
|
60
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
61
|
+
config_data = json.load(f)
|
|
62
|
+
return config_data.get("access_key")
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def validate_access_key(key: str) -> bool:
|
|
69
|
+
"""Validate the access key with the backend database."""
|
|
70
|
+
if not key or not key.strip():
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
from runtime.cli.telemetry import API_BASE_URL
|
|
75
|
+
|
|
76
|
+
url = f"{API_BASE_URL}/api/auth/validate"
|
|
77
|
+
data = json.dumps({"key": key.strip()}).encode("utf-8")
|
|
78
|
+
req = urllib.request.Request(
|
|
79
|
+
url, data=data, headers={"Content-Type": "application/json"}, method="POST"
|
|
80
|
+
)
|
|
81
|
+
with urllib.request.urlopen(req, timeout=2.0) as response:
|
|
82
|
+
res_data = json.loads(response.read().decode("utf-8"))
|
|
83
|
+
return res_data.get("valid", False)
|
|
84
|
+
except urllib.error.HTTPError as e:
|
|
85
|
+
try:
|
|
86
|
+
res_data = json.loads(e.read().decode("utf-8"))
|
|
87
|
+
return res_data.get("valid", False)
|
|
88
|
+
except Exception:
|
|
89
|
+
return False
|
|
90
|
+
except Exception:
|
|
91
|
+
# Network connection error or timeout. We treat this as valid to avoid blocking offline users.
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def ensure_authenticated() -> str:
|
|
96
|
+
"""Ensure the user is authenticated with a valid access key.
|
|
97
|
+
Prompts the user if missing or invalid.
|
|
98
|
+
"""
|
|
99
|
+
console = Console()
|
|
100
|
+
access_key = get_access_key()
|
|
101
|
+
|
|
102
|
+
if access_key:
|
|
103
|
+
if access_key == "opt-out":
|
|
104
|
+
return access_key
|
|
105
|
+
|
|
106
|
+
if validate_access_key(access_key):
|
|
107
|
+
return access_key
|
|
108
|
+
else:
|
|
109
|
+
console.print(
|
|
110
|
+
"[yellow]Stored access key is invalid. Please re-enter.[/yellow]"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
while True:
|
|
114
|
+
if sys.stdout.isatty():
|
|
115
|
+
console.print(
|
|
116
|
+
"\n[dim]Privacy Notice: We collect command usage and demographic data to improve KnowCode.\n"
|
|
117
|
+
"No codebase information is ever collected. If you opt out, no usage data will be collected.[/dim]"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
choice = questionary.select(
|
|
121
|
+
"How would you like to proceed?",
|
|
122
|
+
choices=[
|
|
123
|
+
"Enter Access Key",
|
|
124
|
+
"Opt-out of Telemetry"
|
|
125
|
+
]
|
|
126
|
+
).ask()
|
|
127
|
+
|
|
128
|
+
if choice == "Opt-out of Telemetry":
|
|
129
|
+
save_access_key("opt-out")
|
|
130
|
+
console.print("[dim]Opted out of telemetry. No access code provided.[/dim]\n")
|
|
131
|
+
return "opt-out"
|
|
132
|
+
elif choice == "Enter Access Key":
|
|
133
|
+
access_key = typer.prompt("Enter your access code")
|
|
134
|
+
if validate_access_key(access_key):
|
|
135
|
+
save_access_key(access_key)
|
|
136
|
+
console.print("[green]Access code verified successfully.[/green]")
|
|
137
|
+
return access_key
|
|
138
|
+
else:
|
|
139
|
+
console.print("[red]Invalid access code. Please try again.[/red]")
|
|
140
|
+
else:
|
|
141
|
+
raise KnowcodeError("Authentication aborted.")
|
|
142
|
+
else:
|
|
143
|
+
raise KnowcodeError(
|
|
144
|
+
"Access code is missing or invalid, and terminal is not interactive."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def manage_auth() -> None:
|
|
148
|
+
"""Manage authentication settings interactively (used by 'know auth')."""
|
|
149
|
+
console = Console()
|
|
150
|
+
access_key = get_access_key()
|
|
151
|
+
|
|
152
|
+
if access_key:
|
|
153
|
+
if not sys.stdout.isatty():
|
|
154
|
+
console.print("Authentication preferences already set.")
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
status = "Opted out of telemetry" if access_key == "opt-out" else "Authenticated with Access Key"
|
|
158
|
+
console.print(f"\n[bold cyan]Current Status:[/bold cyan] {status}")
|
|
159
|
+
|
|
160
|
+
choice = questionary.select(
|
|
161
|
+
"Would you like to change your preferences?",
|
|
162
|
+
choices=["Yes, change preferences", "No, exit"]
|
|
163
|
+
).ask()
|
|
164
|
+
|
|
165
|
+
if choice == "Yes, change preferences":
|
|
166
|
+
clear_access_key()
|
|
167
|
+
ensure_authenticated()
|
|
168
|
+
else:
|
|
169
|
+
return
|
|
170
|
+
else:
|
|
171
|
+
ensure_authenticated()
|
runtime/cli/telemetry.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Telemetry logging module for KnowCode CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from runtime.cli.auth import get_access_key
|
|
12
|
+
|
|
13
|
+
# Default local API URL, overrideable via env var
|
|
14
|
+
API_BASE_URL = os.environ.get("KNOWCODE_API_URL", "https://api.knowcode.in")
|
|
15
|
+
TELEMETRY_ENDPOINT = f"{API_BASE_URL}/api/telemetry"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _send_telemetry_sync(
|
|
19
|
+
command: str, status: str, project_id: str, access_key: str
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Send the POST request synchronously. Runs inside the background process."""
|
|
22
|
+
import urllib.request
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
payload = {
|
|
26
|
+
"access_key": access_key,
|
|
27
|
+
"command": command,
|
|
28
|
+
"status": status,
|
|
29
|
+
"project_id": project_id,
|
|
30
|
+
}
|
|
31
|
+
data = json.dumps(payload).encode("utf-8")
|
|
32
|
+
|
|
33
|
+
req = urllib.request.Request(
|
|
34
|
+
TELEMETRY_ENDPOINT,
|
|
35
|
+
data=data,
|
|
36
|
+
headers={
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"User-Agent": "knowcode-cli-telemetry",
|
|
39
|
+
},
|
|
40
|
+
method="POST",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# We can use a longer timeout here because it runs detached
|
|
44
|
+
with urllib.request.urlopen(req, timeout=5.0) as response:
|
|
45
|
+
pass
|
|
46
|
+
except Exception:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def send_telemetry_async(command: str, status: str) -> None:
|
|
51
|
+
"""Dispatches command execution metrics to the backend in a detached background process."""
|
|
52
|
+
if (
|
|
53
|
+
"PYTEST_CURRENT_TEST" in os.environ
|
|
54
|
+
or os.environ.get("KNOWCODE_TESTING") == "true"
|
|
55
|
+
):
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
access_key = get_access_key()
|
|
60
|
+
if not access_key or access_key == "opt-out":
|
|
61
|
+
return # Skip telemetry if the user is not authenticated or opted out
|
|
62
|
+
|
|
63
|
+
# Compute an anonymous hash of the current repository root to identify unique projects safely
|
|
64
|
+
try:
|
|
65
|
+
cwd_str = str(Path.cwd().resolve())
|
|
66
|
+
project_id = hashlib.sha256(cwd_str.encode("utf-8")).hexdigest()[:16]
|
|
67
|
+
except Exception:
|
|
68
|
+
project_id = "unknown"
|
|
69
|
+
|
|
70
|
+
# Dispatch the network request in a separate process so the CLI exits instantly.
|
|
71
|
+
# We only use CREATE_NO_WINDOW on Windows to prevent console window popups.
|
|
72
|
+
# We avoid DETACHED_PROCESS because it forces a new console window to open
|
|
73
|
+
# when running under terminal emulators like Git Bash (Mintty).
|
|
74
|
+
creation_flags = getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
|
75
|
+
|
|
76
|
+
subprocess.Popen(
|
|
77
|
+
[sys.executable, __file__, command, status, project_id, access_key],
|
|
78
|
+
stdout=subprocess.DEVNULL,
|
|
79
|
+
stderr=subprocess.DEVNULL,
|
|
80
|
+
stdin=subprocess.DEVNULL,
|
|
81
|
+
creationflags=creation_flags,
|
|
82
|
+
close_fds=True,
|
|
83
|
+
)
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
if len(sys.argv) == 5:
|
|
90
|
+
_, cmd, stat, proj, key = sys.argv
|
|
91
|
+
_send_telemetry_sync(cmd, stat, proj, key)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Runtime exception hierarchy.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Runtime exceptions.
|
|
2
|
+
|
|
3
|
+
All Runtime-level exceptions inherit from ``KnowcodeError`` so that the
|
|
4
|
+
CLI can catch them uniformly and format them as user-friendly messages
|
|
5
|
+
without stack traces.
|
|
6
|
+
|
|
7
|
+
Hierarchy
|
|
8
|
+
---------
|
|
9
|
+
::
|
|
10
|
+
|
|
11
|
+
KnowcodeError
|
|
12
|
+
├── RepositoryError
|
|
13
|
+
│ ├── NotGitRepository
|
|
14
|
+
│ └── RepositoryNotFound
|
|
15
|
+
├── ArtifactError
|
|
16
|
+
│ ├── KnowcodeAlreadyInitialized
|
|
17
|
+
│ ├── KnowcodeNotInitialized
|
|
18
|
+
│ ├── CorruptKnowcodeArtifact
|
|
19
|
+
│ ├── ScaffoldingFailed
|
|
20
|
+
│ └── TemplateRenderFailed
|
|
21
|
+
├── ConfigError
|
|
22
|
+
│ ├── ConfigNotFound
|
|
23
|
+
│ └── InvalidConfig
|
|
24
|
+
└── SyncError
|
|
25
|
+
├── StructuralEngineFailure
|
|
26
|
+
└── SemanticSyncFailure
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class KnowcodeError(Exception):
|
|
33
|
+
"""Base exception for all domain-level KnowCode errors.
|
|
34
|
+
|
|
35
|
+
Caught by the CLI layer to present a clean, un-traced error message.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RepositoryError(KnowcodeError):
|
|
40
|
+
"""Base exception for repository discovery and path issues."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class NotGitRepository(RepositoryError):
|
|
44
|
+
"""The target directory is not inside a git repository."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RepositoryNotFound(RepositoryError):
|
|
48
|
+
"""Raised when no `.git` directory can be found by walking upward.
|
|
49
|
+
|
|
50
|
+
This indicates KnowCode was invoked outside a valid git repository.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ArtifactError(KnowcodeError):
|
|
55
|
+
"""Base exception for KnowCode artifact filesystem failures."""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class KnowcodeAlreadyInitialized(ArtifactError):
|
|
59
|
+
"""A .knowcode directory already exists in this repository."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class KnowcodeNotInitialized(ArtifactError):
|
|
63
|
+
"""No .knowcode directory found — run ``knowcode .`` first."""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CorruptKnowcodeArtifact(ArtifactError):
|
|
67
|
+
"""The .knowcode directory exists but is structurally invalid."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ScaffoldingFailed(ArtifactError):
|
|
71
|
+
"""Raised when directory creation fails (e.g., permission denied)."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TemplateRenderFailed(ArtifactError):
|
|
75
|
+
"""Raised when a static markdown template fails to render."""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ConfigError(KnowcodeError):
|
|
79
|
+
"""Base exception for configuration loading or parsing failures."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ConfigNotFound(ConfigError):
|
|
83
|
+
"""Raised when `config.yaml` is missing and is strictly required."""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class InvalidConfig(ConfigError):
|
|
87
|
+
"""Raised when `config.yaml` contains malformed or invalid YAML."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SyncError(KnowcodeError):
|
|
91
|
+
"""Base exception for synchronization workflow failures."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class StructuralEngineFailure(SyncError):
|
|
95
|
+
"""The Structural Engine raised an unrecoverable error during sync."""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class SemanticSyncFailure(SyncError):
|
|
99
|
+
"""Raised when the semantic reconciliation workflow fails."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Repository subsystem — creates the common language shared by Runtime and Structural Engine.
|
|
2
|
+
# Components: discovery, paths, validator, models.
|
|
3
|
+
|
|
4
|
+
from runtime.repository.models import Repository, RepositoryPaths
|
|
5
|
+
from runtime.repository.discovery import discover_repository
|
|
6
|
+
from runtime.repository.paths import build_paths
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Repository",
|
|
10
|
+
"RepositoryPaths",
|
|
11
|
+
"discover_repository",
|
|
12
|
+
"build_paths",
|
|
13
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Repository discovery.
|
|
2
|
+
|
|
3
|
+
Locates the repository root by walking up from a starting directory
|
|
4
|
+
until a ``.git`` directory is found.
|
|
5
|
+
|
|
6
|
+
Algorithm
|
|
7
|
+
---------
|
|
8
|
+
::
|
|
9
|
+
|
|
10
|
+
cwd → parent → parent → ... → .git found → repository root
|
|
11
|
+
|
|
12
|
+
Returns a ``Repository`` identity object.
|
|
13
|
+
Raises ``RepositoryNotFound`` if no ``.git`` ancestor exists.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from runtime.exceptions.errors import RepositoryNotFound
|
|
21
|
+
from runtime.repository.models import Repository
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def discover_repository(start: Path | None = None) -> Repository:
|
|
25
|
+
"""Discover the enclosing git repository.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
start : Path | None
|
|
30
|
+
Directory to begin the upward search from.
|
|
31
|
+
Defaults to ``Path.cwd()`` when *None*.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
Repository
|
|
36
|
+
Identity object anchoring all subsequent path derivation.
|
|
37
|
+
|
|
38
|
+
Raises
|
|
39
|
+
------
|
|
40
|
+
RepositoryNotFound
|
|
41
|
+
If no ancestor directory contains a ``.git`` directory.
|
|
42
|
+
"""
|
|
43
|
+
current = (start or Path.cwd()).resolve()
|
|
44
|
+
|
|
45
|
+
# Walk upward through the filesystem hierarchy.
|
|
46
|
+
while True:
|
|
47
|
+
git_dir = current / ".git"
|
|
48
|
+
if git_dir.is_dir():
|
|
49
|
+
return Repository(
|
|
50
|
+
root=current,
|
|
51
|
+
git_dir=git_dir,
|
|
52
|
+
knowcode_dir=current / ".knowcode",
|
|
53
|
+
agent_dir=current / ".agent",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
parent = current.parent
|
|
57
|
+
if parent == current:
|
|
58
|
+
# Reached filesystem root without finding .git.
|
|
59
|
+
break
|
|
60
|
+
current = parent
|
|
61
|
+
|
|
62
|
+
raise RepositoryNotFound(
|
|
63
|
+
f"No git repository found at or above: {start or Path.cwd()}"
|
|
64
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Repository domain models.
|
|
2
|
+
|
|
3
|
+
Defines the two foundational data contracts that form the common language
|
|
4
|
+
between the Runtime and the Structural Engine:
|
|
5
|
+
|
|
6
|
+
- Repository: lightweight identity object for a discovered git repository.
|
|
7
|
+
- RepositoryPaths: the canonical 9-field path contract passed to every
|
|
8
|
+
subsystem. No component should hardcode paths like ".knowcode/structure".
|
|
9
|
+
Everything comes from RepositoryPaths.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, ConfigDict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Repository(BaseModel):
|
|
20
|
+
"""Lightweight identity of a discovered git repository.
|
|
21
|
+
|
|
22
|
+
Produced by ``discover_repository()``, consumed by ``build_paths()``.
|
|
23
|
+
Contains only the three root-level anchors needed to derive all other
|
|
24
|
+
paths.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
model_config = ConfigDict(frozen=True)
|
|
28
|
+
|
|
29
|
+
root: Path
|
|
30
|
+
"""Absolute path to the repository root (the directory containing .git)."""
|
|
31
|
+
|
|
32
|
+
git_dir: Path
|
|
33
|
+
"""Absolute path to the .git directory."""
|
|
34
|
+
|
|
35
|
+
knowcode_dir: Path
|
|
36
|
+
"""Absolute path to the .knowcode directory (may or may not exist yet)."""
|
|
37
|
+
|
|
38
|
+
agent_dir: Path
|
|
39
|
+
"""Absolute path to the .agent directory (may or may not exist yet)."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class RepositoryPaths(BaseModel):
|
|
43
|
+
"""Canonical paths for the entire ecosystem.
|
|
44
|
+
|
|
45
|
+
This is the single source of truth for all filesystem locations.
|
|
46
|
+
All paths are absolute.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
model_config = ConfigDict(frozen=True)
|
|
50
|
+
|
|
51
|
+
repo_root: Path
|
|
52
|
+
"""The root of the user's physical repository containing the ``.git`` folder."""
|
|
53
|
+
|
|
54
|
+
git_dir: Path
|
|
55
|
+
"""``.git/`` directory."""
|
|
56
|
+
|
|
57
|
+
knowcode_root: Path
|
|
58
|
+
"""``.knowcode/`` - The top-level KnowCode Artifact directory."""
|
|
59
|
+
|
|
60
|
+
knowcode_file: Path
|
|
61
|
+
"""``.knowcode/KNOWCODE.md`` - The human-readable entrypoint document."""
|
|
62
|
+
|
|
63
|
+
state_file: Path
|
|
64
|
+
"""``.knowcode/state.yaml`` - The definitive synchronization state registry."""
|
|
65
|
+
|
|
66
|
+
structure_dir: Path
|
|
67
|
+
"""``.knowcode/structure/`` - Base directory for structural artifacts."""
|
|
68
|
+
|
|
69
|
+
snapshots_dir: Path
|
|
70
|
+
"""``.knowcode/structure/snapshots/`` - Directory containing S-XXX.json files."""
|
|
71
|
+
|
|
72
|
+
reports_dir: Path
|
|
73
|
+
"""``.knowcode/reports/`` - Directory containing R-XXX.md differential reports."""
|
|
74
|
+
|
|
75
|
+
logs_dir: Path
|
|
76
|
+
"""``.knowcode/logs/`` - Internal system logs."""
|
|
77
|
+
|
|
78
|
+
knowledge_dir: Path
|
|
79
|
+
"""``.knowcode/knowledge/`` - The AI-managed semantic architecture base directory."""
|
|
80
|
+
|
|
81
|
+
agent_dir: Path
|
|
82
|
+
"""``.agent/`` - Semantic Governance and Memory Layer root."""
|
|
83
|
+
|
|
84
|
+
workflows_dir: Path
|
|
85
|
+
"""``.agent/workflows/`` - User-defined orchestration playbooks."""
|
|
86
|
+
|
|
87
|
+
skills_dir: Path
|
|
88
|
+
"""``.agent/skills/`` - Custom agent capability scripts."""
|
|
89
|
+
|
|
90
|
+
memory_dir: Path
|
|
91
|
+
"""``.agent/memory/`` - The short-term semantic working memory buffer."""
|
|
92
|
+
|
|
93
|
+
active_context_file: Path
|
|
94
|
+
"""``.agent/memory/active_context.md`` - the live intent-tracking scratchpad."""
|
|
95
|
+
|
|
96
|
+
previous_context_file: Path
|
|
97
|
+
"""``.agent/memory/previous_context.md`` - the rolled-over intent log waiting for synthesis."""
|
|
98
|
+
|
|
99
|
+
system_skills_dir: Path
|
|
100
|
+
"""``.agent/skills/system/`` - prime directives and system-level skills."""
|
|
101
|
+
|
|
102
|
+
raw_knowledge_dir: Path
|
|
103
|
+
"""``.knowcode/knowledge/raw/`` - inbox for legacy documentation to be ingested."""
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Repository path builder.
|
|
2
|
+
|
|
3
|
+
Transforms a ``Repository`` identity into the canonical 9-field
|
|
4
|
+
``RepositoryPaths`` contract.
|
|
5
|
+
|
|
6
|
+
This module performs pure path generation.
|
|
7
|
+
No validation occurs here. No filesystem access.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from runtime.repository.models import Repository, RepositoryPaths
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_paths(repo: Repository) -> RepositoryPaths:
|
|
16
|
+
"""Build the full path contract from a discovered repository identity.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
repo : Repository
|
|
21
|
+
The base identity object containing the root anchors.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
RepositoryPaths
|
|
26
|
+
The canonical, immutable path contract for the ecosystem.
|
|
27
|
+
"""
|
|
28
|
+
knowcode = repo.knowcode_dir
|
|
29
|
+
agent = repo.agent_dir
|
|
30
|
+
|
|
31
|
+
return RepositoryPaths(
|
|
32
|
+
repo_root=repo.root,
|
|
33
|
+
git_dir=repo.git_dir,
|
|
34
|
+
knowcode_root=knowcode,
|
|
35
|
+
knowcode_file=knowcode / "KNOWCODE.md",
|
|
36
|
+
state_file=knowcode / "state.yaml",
|
|
37
|
+
structure_dir=knowcode / "structure",
|
|
38
|
+
snapshots_dir=knowcode / "structure" / "snapshots",
|
|
39
|
+
reports_dir=knowcode / "reports",
|
|
40
|
+
logs_dir=knowcode / "logs",
|
|
41
|
+
knowledge_dir=knowcode / "knowledge",
|
|
42
|
+
agent_dir=agent,
|
|
43
|
+
workflows_dir=agent / "workflows",
|
|
44
|
+
skills_dir=agent / "skills",
|
|
45
|
+
memory_dir=agent / "memory",
|
|
46
|
+
active_context_file=agent / "memory" / "active_context.md",
|
|
47
|
+
previous_context_file=agent / "memory" / "previous_context.md",
|
|
48
|
+
system_skills_dir=agent / "skills" / "system",
|
|
49
|
+
raw_knowledge_dir=knowcode / "knowledge" / "raw",
|
|
50
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Repository integrity validator.
|
|
2
|
+
|
|
3
|
+
Verifies that a discovered repository meets the preconditions required
|
|
4
|
+
by a given operation. Validation is *contextual* — initialization has
|
|
5
|
+
different requirements than synchronization.
|
|
6
|
+
|
|
7
|
+
Checks
|
|
8
|
+
------
|
|
9
|
+
- Git repository exists (``.git``).
|
|
10
|
+
- KnowCode artifact initialized (``.knowcode``).
|
|
11
|
+
- ``state.yaml`` exists.
|
|
12
|
+
- ``structure/`` folder exists.
|
|
13
|
+
|
|
14
|
+
Raises typed exceptions from ``runtime.exceptions.errors``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from runtime.exceptions.errors import (
|
|
20
|
+
KnowcodeAlreadyInitialized,
|
|
21
|
+
KnowcodeNotInitialized,
|
|
22
|
+
CorruptKnowcodeArtifact,
|
|
23
|
+
NotGitRepository,
|
|
24
|
+
)
|
|
25
|
+
from runtime.repository.models import RepositoryPaths
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_for_init(paths: RepositoryPaths) -> None:
|
|
29
|
+
"""Validate preconditions for ``knowcode .`` (initialization).
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
paths : RepositoryPaths
|
|
34
|
+
Canonical paths derived from the discovered repository.
|
|
35
|
+
|
|
36
|
+
Raises
|
|
37
|
+
------
|
|
38
|
+
NotGitRepository
|
|
39
|
+
If the ``.git`` directory does not exist.
|
|
40
|
+
KnowcodeAlreadyInitialized
|
|
41
|
+
If a ``.knowcode`` directory already exists.
|
|
42
|
+
"""
|
|
43
|
+
git_dir = paths.repo_root / ".git"
|
|
44
|
+
if not git_dir.is_dir():
|
|
45
|
+
raise NotGitRepository(
|
|
46
|
+
f"Not a git repository: {paths.repo_root}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if paths.knowcode_root.is_dir():
|
|
50
|
+
raise KnowcodeAlreadyInitialized(
|
|
51
|
+
f"KnowCode artifact already exists: {paths.knowcode_root}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if paths.agent_dir.is_dir():
|
|
55
|
+
raise KnowcodeAlreadyInitialized(
|
|
56
|
+
f"Agent artifact already exists: {paths.agent_dir}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def validate_for_sync(paths: RepositoryPaths) -> None:
|
|
60
|
+
"""Validate preconditions for ``knowcode sync`` or ``knowcode status``.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
paths : RepositoryPaths
|
|
65
|
+
Canonical paths derived from the discovered repository.
|
|
66
|
+
|
|
67
|
+
Raises
|
|
68
|
+
------
|
|
69
|
+
NotGitRepository
|
|
70
|
+
If the ``.git`` directory does not exist.
|
|
71
|
+
KnowcodeNotInitialized
|
|
72
|
+
If the ``.knowcode`` directory does not exist.
|
|
73
|
+
CorruptKnowcodeArtifact
|
|
74
|
+
If ``.knowcode`` exists but critical internal structure is missing.
|
|
75
|
+
"""
|
|
76
|
+
git_dir = paths.repo_root / ".git"
|
|
77
|
+
if not git_dir.is_dir():
|
|
78
|
+
raise NotGitRepository(
|
|
79
|
+
f"Not a git repository: {paths.repo_root}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if not paths.knowcode_root.is_dir():
|
|
83
|
+
raise KnowcodeNotInitialized(
|
|
84
|
+
f"KnowCode artifact not found. Run 'know .' first: {paths.repo_root}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if not paths.agent_dir.is_dir():
|
|
88
|
+
raise KnowcodeNotInitialized(
|
|
89
|
+
f"Agent artifact not found. Run 'know .' first: {paths.repo_root}"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if not paths.state_file.is_file():
|
|
93
|
+
raise CorruptKnowcodeArtifact(
|
|
94
|
+
f"state.yaml missing: {paths.state_file}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if not paths.structure_dir.is_dir():
|
|
98
|
+
raise CorruptKnowcodeArtifact(
|
|
99
|
+
f"structure directory missing: {paths.structure_dir}"
|
|
100
|
+
)
|