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.
Files changed (63) hide show
  1. knowcode-0.1.0.dist-info/METADATA +175 -0
  2. knowcode-0.1.0.dist-info/RECORD +63 -0
  3. knowcode-0.1.0.dist-info/WHEEL +4 -0
  4. knowcode-0.1.0.dist-info/entry_points.txt +2 -0
  5. runtime/__init__.py +4 -0
  6. runtime/artifact/__init__.py +1 -0
  7. runtime/artifact/builder.py +179 -0
  8. runtime/cli/__init__.py +1 -0
  9. runtime/cli/animation.py +278 -0
  10. runtime/cli/app.py +309 -0
  11. runtime/cli/auth.py +171 -0
  12. runtime/cli/telemetry.py +91 -0
  13. runtime/exceptions/__init__.py +1 -0
  14. runtime/exceptions/errors.py +99 -0
  15. runtime/repository/__init__.py +13 -0
  16. runtime/repository/discovery.py +64 -0
  17. runtime/repository/models.py +103 -0
  18. runtime/repository/paths.py +50 -0
  19. runtime/repository/validator.py +100 -0
  20. runtime/services/__init__.py +1 -0
  21. runtime/services/ingest_service.py +105 -0
  22. runtime/services/init_service.py +45 -0
  23. runtime/services/semantic_sync_service.py +55 -0
  24. runtime/services/status_service.py +40 -0
  25. runtime/services/sync_service.py +57 -0
  26. runtime/templates/KNOWCODE_LOADER.md.j2 +24 -0
  27. runtime/templates/README_KNOWLEDGE.md.j2 +12 -0
  28. runtime/templates/README_STRUCTURE.md.j2 +19 -0
  29. runtime/templates/__init__.py +1 -0
  30. runtime/templates/active_context.md.j2 +3 -0
  31. runtime/templates/ingest_legacy.md.j2 +15 -0
  32. runtime/templates/raw_readme.md.j2 +9 -0
  33. runtime/templates/sync_reconciliation.md.j2 +17 -0
  34. runtime/templates/synthesize_knowledge.md.j2 +32 -0
  35. runtime/templates/track_intent.md.j2 +14 -0
  36. structural_engine/__init__.py +3 -0
  37. structural_engine/diff/__init__.py +1 -0
  38. structural_engine/diff/generator.py +92 -0
  39. structural_engine/diff/models.py +48 -0
  40. structural_engine/engine.py +192 -0
  41. structural_engine/logs/__init__.py +1 -0
  42. structural_engine/logs/generator.py +33 -0
  43. structural_engine/parser/__init__.py +7 -0
  44. structural_engine/parser/discovery.py +165 -0
  45. structural_engine/parser/extractors/base.py +44 -0
  46. structural_engine/parser/languages/javascript/adapter.py +149 -0
  47. structural_engine/parser/languages/python/adapter.py +174 -0
  48. structural_engine/parser/languages/typescript/adapter.py +165 -0
  49. structural_engine/parser/models.py +186 -0
  50. structural_engine/parser/parser.py +160 -0
  51. structural_engine/parser/resolvers/calls.py +105 -0
  52. structural_engine/parser/tree_sitter/registry.py +61 -0
  53. structural_engine/reports/__init__.py +1 -0
  54. structural_engine/reports/generator.py +77 -0
  55. structural_engine/results.py +54 -0
  56. structural_engine/revisions/__init__.py +1 -0
  57. structural_engine/revisions/tracker.py +32 -0
  58. structural_engine/snapshot/__init__.py +1 -0
  59. structural_engine/snapshot/generator.py +58 -0
  60. structural_engine/snapshot/loader.py +59 -0
  61. structural_engine/state/__init__.py +1 -0
  62. structural_engine/state/manager.py +169 -0
  63. 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()
@@ -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
+ )