engramkeep 0.5.1__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.
engramkeep/concepts.py ADDED
@@ -0,0 +1,198 @@
1
+ """Deterministic bilingual (EN/TR) concept expansion for keyword search.
2
+
3
+ Engramkeep stores decisions in plain Markdown. People write memory cards in
4
+ the language they think in: a card saved as "Capacitor Android keystore
5
+ sorunu" must still surface for the query "mobile" or "keystore". This module
6
+ maps common engineering concepts to their EN and TR spellings so a search in
7
+ one language finds cards written in the other.
8
+
9
+ The map is deliberately small, generic, and free of project-specific terms.
10
+ It is a scoring aid, not an ontology: matched expanded terms carry a lower
11
+ weight than the literal query tokens.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+
18
+ SEMANTIC_CONCEPTS: dict[str, tuple[str, ...]] = {
19
+ "mobile": (
20
+ "mobil",
21
+ "android",
22
+ "ios",
23
+ "iphone",
24
+ "apk",
25
+ "capacitor",
26
+ "react-native",
27
+ "flutter",
28
+ "keystore",
29
+ "hybrid",
30
+ ),
31
+ "database": (
32
+ "veritabanı",
33
+ "sqlite",
34
+ "postgres",
35
+ "postgresql",
36
+ "mysql",
37
+ "mariadb",
38
+ "mongodb",
39
+ "redis",
40
+ "prisma",
41
+ "sql",
42
+ "db",
43
+ "storage",
44
+ ),
45
+ "frontend": (
46
+ "ön yüz",
47
+ "arayüz",
48
+ "react",
49
+ "next.js",
50
+ "nextjs",
51
+ "vue",
52
+ "svelte",
53
+ "pwa",
54
+ "tailwind",
55
+ "css",
56
+ "component",
57
+ "ui",
58
+ "ux",
59
+ ),
60
+ "backend": (
61
+ "api",
62
+ "rest",
63
+ "graphql",
64
+ "fastapi",
65
+ "flask",
66
+ "django",
67
+ "express",
68
+ "node",
69
+ "microservice",
70
+ "endpoint",
71
+ "arka yüz",
72
+ ),
73
+ "auth": (
74
+ "authentication",
75
+ "authorization",
76
+ "jwt",
77
+ "oauth",
78
+ "session",
79
+ "login",
80
+ "token",
81
+ "kimlik",
82
+ "yetki",
83
+ "şifre",
84
+ ),
85
+ "devops": (
86
+ "docker",
87
+ "kubernetes",
88
+ "k8s",
89
+ "ci/cd",
90
+ "github actions",
91
+ "vercel",
92
+ "aws",
93
+ "cloudflare",
94
+ "nginx",
95
+ "deploy",
96
+ "dağıtım",
97
+ ),
98
+ "automation": (
99
+ "otomasyon",
100
+ "webhook",
101
+ "workflow",
102
+ "cron",
103
+ "schedule",
104
+ "bot",
105
+ "scraper",
106
+ "pipeline",
107
+ ),
108
+ "testing": (
109
+ "test",
110
+ "sınama",
111
+ "pytest",
112
+ "unittest",
113
+ "e2e",
114
+ "integration",
115
+ "mock",
116
+ "fixture",
117
+ ),
118
+ "security": (
119
+ "güvenlik",
120
+ "vulnerability",
121
+ "xss",
122
+ "csrf",
123
+ "injection",
124
+ "redact",
125
+ "secret",
126
+ "encrypt",
127
+ "şifreleme",
128
+ ),
129
+ "search": (
130
+ "arama",
131
+ "query",
132
+ "index",
133
+ "indeks",
134
+ "full-text",
135
+ "fts",
136
+ "rank",
137
+ "snippet",
138
+ ),
139
+ "sync": (
140
+ "senkronizasyon",
141
+ "senkron",
142
+ "backup",
143
+ "yedek",
144
+ "restore",
145
+ "cloud",
146
+ "drive",
147
+ "sync",
148
+ ),
149
+ "performance": (
150
+ "performans",
151
+ "latency",
152
+ "cache",
153
+ "önbellek",
154
+ "profiling",
155
+ "optimizasyon",
156
+ "memory",
157
+ "bellek",
158
+ ),
159
+ "media": (
160
+ "görsel",
161
+ "image",
162
+ "video",
163
+ "audio",
164
+ "ffmpeg",
165
+ "logo",
166
+ "svg",
167
+ "banner",
168
+ "thumbnail",
169
+ ),
170
+ }
171
+
172
+ _TOKEN_SPLIT = re.compile(r"[^\w/.@-]+", re.UNICODE)
173
+
174
+
175
+ def expand_query_terms(query: str) -> set[str]:
176
+ """Return the normalized query terms plus matching EN/TR concept terms."""
177
+ normalized = " ".join(query.lower().split())
178
+ if not normalized:
179
+ return set()
180
+
181
+ tokens = {token for token in _TOKEN_SPLIT.split(normalized) if token}
182
+ expanded = set(tokens)
183
+ if not tokens:
184
+ expanded.add(normalized)
185
+
186
+ for primary, related in SEMANTIC_CONCEPTS.items():
187
+ family = (primary, *related)
188
+ if any(_term_matches(term, normalized, tokens) for term in family):
189
+ expanded.update(family)
190
+ return expanded
191
+
192
+
193
+ def _term_matches(term: str, normalized: str, tokens: set[str]) -> bool:
194
+ if term in tokens:
195
+ return True
196
+ # Substring match in both directions so "mobil" finds "mobile"-family
197
+ # entries and multi-word terms like "github actions" still match.
198
+ return term in normalized or normalized in term
engramkeep/config.py ADDED
@@ -0,0 +1,79 @@
1
+ """Resolve the local Engramkeep data directory.
2
+
3
+ Precedence is intentionally simple and explicit:
4
+
5
+ 1. the ``--home`` command-line value,
6
+ 2. the ``ENGRAMKEEP_HOME`` environment variable,
7
+ 3. a platform-specific default.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import platform
14
+ from collections.abc import Mapping
15
+ from pathlib import Path
16
+
17
+ from .errors import ConfigError
18
+
19
+ APP_NAME = "engramkeep"
20
+ DISPLAY_NAME = "Engramkeep"
21
+ ENV_HOME = "ENGRAMKEEP_HOME"
22
+ SCHEMA_VERSION = 1
23
+
24
+
25
+ def platform_default_home(
26
+ *,
27
+ system: str | None = None,
28
+ env: Mapping[str, str] | None = None,
29
+ user_home: Path | None = None,
30
+ ) -> Path:
31
+ """Return the platform-specific default data directory."""
32
+ system = system or platform.system()
33
+ env = os.environ if env is None else env
34
+ user_home = Path.home() if user_home is None else Path(user_home)
35
+
36
+ if system == "Windows":
37
+ base = env.get("LOCALAPPDATA")
38
+ root = Path(base) if base else user_home / "AppData" / "Local"
39
+ return (root / DISPLAY_NAME).resolve()
40
+ if system == "Darwin":
41
+ return (user_home / "Library" / "Application Support" / DISPLAY_NAME).resolve()
42
+ base = env.get("XDG_DATA_HOME")
43
+ root = Path(base) if base else user_home / ".local" / "share"
44
+ return (root / APP_NAME).resolve()
45
+
46
+
47
+ def resolve_home(
48
+ cli_home: str | os.PathLike[str] | None = None,
49
+ *,
50
+ env: Mapping[str, str] | None = None,
51
+ system: str | None = None,
52
+ user_home: Path | None = None,
53
+ ) -> Path:
54
+ """Resolve the active memory home.
55
+
56
+ An explicit command-line value wins over ``ENGRAMKEEP_HOME``, which wins
57
+ over the platform default. Empty values are ignored.
58
+ """
59
+ env = os.environ if env is None else env
60
+ raw: str | os.PathLike[str] | None = cli_home
61
+ source = "command line"
62
+
63
+ if raw is None or str(raw).strip() == "":
64
+ raw = env.get(ENV_HOME)
65
+ source = ENV_HOME
66
+ if raw is None or str(raw).strip() == "":
67
+ return platform_default_home(system=system, env=env, user_home=user_home)
68
+
69
+ path = Path(raw).expanduser()
70
+ try:
71
+ resolved = path.resolve()
72
+ except OSError as exc:
73
+ raise ConfigError(f"cannot resolve {source} path: {path}") from exc
74
+
75
+ if resolved == Path(resolved.anchor):
76
+ raise ConfigError(f"{source} must not point at a filesystem root: {resolved}")
77
+ if resolved.exists() and not resolved.is_dir():
78
+ raise ConfigError(f"{source} is not a directory: {resolved}")
79
+ return resolved