open-knowledge-studio 0.2.2__tar.gz → 0.2.3__tar.gz
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.
- {open_knowledge_studio-0.2.2/open_knowledge_studio.egg-info → open_knowledge_studio-0.2.3}/PKG-INFO +1 -1
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/user-prompt-recall.sh +3 -1
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/cli.py +78 -4
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/config.py +11 -3
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/metrics.py +7 -3
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/store.py +32 -17
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3/open_knowledge_studio.egg-info}/PKG-INFO +1 -1
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/SOURCES.txt +1 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/pyproject.toml +1 -1
- open_knowledge_studio-0.2.3/setup.py +42 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/LICENSE +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/MANIFEST.in +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/README.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/__init__.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/_meta/frontmatter-schema.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/_meta/learning-schema.json +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/_meta/raw-evidence-schema.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/pre-compact.sh +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/session-start.sh +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/user-prompt-recall.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/hooks/validate-wiki-write.sh +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/rules/raw-intake.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/rules/wiki-writing.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/settings.json +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/archive/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/compile/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/ingest/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/lint/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/promote/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/query/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/start/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/claude/skills/status/SKILL.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/settings/handlers.json +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/settings/input-sources.json +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/settings/raw-tools.example.json +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/anti-pattern.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/concept.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/draft.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/_assets/templates/strategy.md +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/distiller.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/health.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/knowledge_studio/recall.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/dependency_links.txt +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/entry_points.txt +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/requires.txt +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/open_knowledge_studio.egg-info/top_level.txt +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/setup.cfg +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_distiller.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_health.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_init.py +0 -0
- {open_knowledge_studio-0.2.2 → open_knowledge_studio-0.2.3}/tests/test_recall.py +0 -0
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
# Passes the original stdin (editor JSON payload) through to the Python hook,
|
|
4
4
|
# and drops jieba's stderr chatter so only the clean <recalled-memory> block
|
|
5
5
|
# reaches stdout. Fails open (exit 0) so a prompt is never blocked.
|
|
6
|
-
|
|
6
|
+
# OKS_PYTHON is baked in by `oks hook install` to point at the interpreter
|
|
7
|
+
# that can import knowledge_studio (pipx/venv safe); falls back to python3.
|
|
8
|
+
exec "${OKS_PYTHON:-python3}" "$(dirname "$0")/user-prompt-recall.py" 2>/dev/null
|
|
@@ -24,6 +24,27 @@ app = typer.Typer(
|
|
|
24
24
|
)
|
|
25
25
|
console = Console()
|
|
26
26
|
|
|
27
|
+
|
|
28
|
+
def _version_callback(value: bool):
|
|
29
|
+
if value:
|
|
30
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
31
|
+
try:
|
|
32
|
+
console.print(f"oks {version('open-knowledge-studio')}")
|
|
33
|
+
except PackageNotFoundError:
|
|
34
|
+
console.print("oks (development, not installed as a package)")
|
|
35
|
+
raise typer.Exit()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.callback()
|
|
39
|
+
def _main(
|
|
40
|
+
version: bool = typer.Option(
|
|
41
|
+
False, "--version", "-V", callback=_version_callback, is_eager=True,
|
|
42
|
+
help="Show the oks version and exit.",
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
27
48
|
wiki_app = typer.Typer(help="Wiki page management.")
|
|
28
49
|
drafts_app = typer.Typer(help="Draft proposal management.")
|
|
29
50
|
config_app = typer.Typer(help="Global configuration (~/.oks/config.json).")
|
|
@@ -191,8 +212,18 @@ def wiki_create(
|
|
|
191
212
|
if not content and not sys.stdin.isatty():
|
|
192
213
|
content = sys.stdin.read()
|
|
193
214
|
|
|
194
|
-
type_map = {
|
|
195
|
-
|
|
215
|
+
type_map = {
|
|
216
|
+
"concept": "concepts", "concepts": "concepts",
|
|
217
|
+
"strategy": "strategies", "strategies": "strategies",
|
|
218
|
+
"anti-pattern": "anti-patterns", "anti-patterns": "anti-patterns",
|
|
219
|
+
}
|
|
220
|
+
wiki_type = type_map.get(page_type)
|
|
221
|
+
if wiki_type is None:
|
|
222
|
+
console.print(
|
|
223
|
+
f"[yellow]Unknown --type '{page_type}' — using 'concept'. "
|
|
224
|
+
f"Valid: concept, strategy, anti-pattern.[/yellow]"
|
|
225
|
+
)
|
|
226
|
+
wiki_type = "concepts"
|
|
196
227
|
|
|
197
228
|
path = store.write_wiki_page(
|
|
198
229
|
title=title,
|
|
@@ -617,7 +648,11 @@ def init(
|
|
|
617
648
|
|
|
618
649
|
base, is_packaged = _asset_source()
|
|
619
650
|
if base is None:
|
|
620
|
-
console.print(
|
|
651
|
+
console.print(
|
|
652
|
+
"[yellow]No bundled assets found — skills/templates not materialized.[/yellow]\n"
|
|
653
|
+
" Source installs lack the asset bundle. Fix: pip install open-knowledge-studio,\n"
|
|
654
|
+
" or run python cli/scripts/bundle_assets.py in the repo before installing."
|
|
655
|
+
)
|
|
621
656
|
else:
|
|
622
657
|
copied = _materialize_assets(root, base, is_packaged, overwrite=upgrade)
|
|
623
658
|
if copied:
|
|
@@ -696,6 +731,12 @@ def _ensure_recall_scripts(root: Path) -> list[str]:
|
|
|
696
731
|
f"bundled hook script not found: {name} (asset source: {src_dir})"
|
|
697
732
|
)
|
|
698
733
|
shutil.copy2(src_dir / name, dest)
|
|
734
|
+
if name.endswith(".sh"):
|
|
735
|
+
import sys
|
|
736
|
+
text = dest.read_text(encoding="utf-8").replace(
|
|
737
|
+
'"${OKS_PYTHON:-python3}"', f'"${{OKS_PYTHON:-{sys.executable}}}"'
|
|
738
|
+
)
|
|
739
|
+
dest.write_text(text, encoding="utf-8")
|
|
699
740
|
dest.chmod(dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
700
741
|
created.append(name)
|
|
701
742
|
return created
|
|
@@ -757,12 +798,29 @@ def hook_install(
|
|
|
757
798
|
console.print("[red]--editor must be one of: claude, qoder, both[/red]")
|
|
758
799
|
raise typer.Exit(1)
|
|
759
800
|
|
|
801
|
+
import platform
|
|
802
|
+
if platform.system() == "Windows":
|
|
803
|
+
console.print(
|
|
804
|
+
"[yellow]Warning: hooks are bash scripts and will not run on native Windows.[/yellow]\n"
|
|
805
|
+
" Use WSL (or Git Bash configured as the hook shell) for auto-recall to work."
|
|
806
|
+
)
|
|
807
|
+
|
|
760
808
|
root = _instance_root(path)
|
|
761
809
|
if not root.is_dir():
|
|
762
810
|
console.print(f"[red]Instance root not found:[/red] {root}")
|
|
763
811
|
raise typer.Exit(1)
|
|
764
812
|
|
|
765
|
-
|
|
813
|
+
try:
|
|
814
|
+
created = _ensure_recall_scripts(root)
|
|
815
|
+
except FileNotFoundError as e:
|
|
816
|
+
console.print(
|
|
817
|
+
f"[red]Cannot install hook — bundled assets missing.[/red]\n"
|
|
818
|
+
f" {e}\n"
|
|
819
|
+
f" This happens when oks was installed from source without the asset bundle.\n"
|
|
820
|
+
f" Fix: [bold]pip install open-knowledge-studio[/bold] (PyPI wheel includes assets),\n"
|
|
821
|
+
f" or run [bold]python cli/scripts/bundle_assets.py[/bold] in the repo before installing."
|
|
822
|
+
)
|
|
823
|
+
raise typer.Exit(1)
|
|
766
824
|
if created:
|
|
767
825
|
console.print(f"[green]Installed hook script:[/green] {', '.join(created)}")
|
|
768
826
|
|
|
@@ -788,6 +846,22 @@ def hook_status(
|
|
|
788
846
|
script = root / ".claude" / "hooks" / "user-prompt-recall.sh"
|
|
789
847
|
console.print(f"[bold]Instance:[/bold] {root}")
|
|
790
848
|
console.print(f" script: {'present' if script.is_file() else 'missing'} ({script})")
|
|
849
|
+
if script.is_file():
|
|
850
|
+
import os
|
|
851
|
+
import re
|
|
852
|
+
import subprocess
|
|
853
|
+
m = re.search(r"\$\{OKS_PYTHON:-([^}]+)\}", script.read_text(encoding="utf-8"))
|
|
854
|
+
py = os.environ.get("OKS_PYTHON") or (m.group(1) if m else "python3")
|
|
855
|
+
try:
|
|
856
|
+
ok = subprocess.run(
|
|
857
|
+
[py, "-c", "import knowledge_studio"],
|
|
858
|
+
capture_output=True, timeout=15,
|
|
859
|
+
).returncode == 0
|
|
860
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
861
|
+
ok = False
|
|
862
|
+
state = ("[green]importable[/green]" if ok
|
|
863
|
+
else "[red]NOT importable — hook will silently no-op; re-run oks hook install[/red]")
|
|
864
|
+
console.print(f" engine: {state} (python: {py})")
|
|
791
865
|
for name, rel in _HOOK_EDITORS.items():
|
|
792
866
|
settings_path = root / rel
|
|
793
867
|
wired = _hook_is_wired(settings_path, _RECALL_HOOK_CMD)
|
|
@@ -61,8 +61,14 @@ def load_config() -> dict[str, Any]:
|
|
|
61
61
|
path = config_path()
|
|
62
62
|
if not path.exists():
|
|
63
63
|
return dict(DEFAULT_CONFIG)
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
try:
|
|
65
|
+
with open(path) as f:
|
|
66
|
+
return json.load(f)
|
|
67
|
+
except json.JSONDecodeError as e:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"{path} is corrupt ({e}). Fix the JSON manually, or delete it and "
|
|
70
|
+
f"re-run `oks config init` / `oks init <path>`."
|
|
71
|
+
) from e
|
|
66
72
|
|
|
67
73
|
|
|
68
74
|
def save_config(config: dict[str, Any]) -> None:
|
|
@@ -70,6 +76,7 @@ def save_config(config: dict[str, Any]) -> None:
|
|
|
70
76
|
path = config_path()
|
|
71
77
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
72
78
|
|
|
79
|
+
import contextlib
|
|
73
80
|
import tempfile
|
|
74
81
|
fd, tmp = tempfile.mkstemp(dir=str(path.parent), suffix=".tmp")
|
|
75
82
|
try:
|
|
@@ -77,7 +84,8 @@ def save_config(config: dict[str, Any]) -> None:
|
|
|
77
84
|
json.dump(config, f, indent=2, ensure_ascii=False)
|
|
78
85
|
os.replace(tmp, path)
|
|
79
86
|
except Exception:
|
|
80
|
-
|
|
87
|
+
with contextlib.suppress(OSError):
|
|
88
|
+
os.unlink(tmp)
|
|
81
89
|
raise
|
|
82
90
|
|
|
83
91
|
|
|
@@ -5,7 +5,7 @@ Removed KnowledgeStore dependency. Uses store functions.
|
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from datetime import UTC, datetime, timedelta
|
|
8
|
+
from datetime import UTC, date, datetime, timedelta
|
|
9
9
|
|
|
10
10
|
from knowledge_studio.store import list_wiki_pages, repo_root, wiki_dir
|
|
11
11
|
|
|
@@ -86,11 +86,15 @@ def _compute_credibility(pages: list[dict], ninety_days_ago: datetime) -> dict:
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
def _parse_date(value
|
|
89
|
+
def _parse_date(value) -> datetime:
|
|
90
90
|
if not value:
|
|
91
91
|
return datetime.min.replace(tzinfo=UTC)
|
|
92
|
+
if isinstance(value, datetime):
|
|
93
|
+
return value if value.tzinfo else value.replace(tzinfo=UTC)
|
|
94
|
+
if isinstance(value, date):
|
|
95
|
+
return datetime(value.year, value.month, value.day, tzinfo=UTC)
|
|
92
96
|
try:
|
|
93
|
-
dt = datetime.fromisoformat(value)
|
|
97
|
+
dt = datetime.fromisoformat(str(value))
|
|
94
98
|
if dt.tzinfo is None:
|
|
95
99
|
dt = dt.replace(tzinfo=UTC)
|
|
96
100
|
return dt
|
|
@@ -13,7 +13,7 @@ import math
|
|
|
13
13
|
import os
|
|
14
14
|
import re
|
|
15
15
|
import tempfile
|
|
16
|
-
from datetime import UTC, datetime
|
|
16
|
+
from datetime import UTC, date, datetime
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
19
|
import yaml
|
|
@@ -44,8 +44,13 @@ def repo_root() -> Path:
|
|
|
44
44
|
kb_path = load_config().get("knowledge_base_path")
|
|
45
45
|
if kb_path:
|
|
46
46
|
return Path(kb_path)
|
|
47
|
-
except Exception:
|
|
48
|
-
|
|
47
|
+
except Exception as e:
|
|
48
|
+
import sys
|
|
49
|
+
print(
|
|
50
|
+
f"oks: warning: could not read ~/.oks/config.json ({e}); "
|
|
51
|
+
f"falling back to current directory as KB root",
|
|
52
|
+
file=sys.stderr,
|
|
53
|
+
)
|
|
49
54
|
return Path(os.getcwd())
|
|
50
55
|
|
|
51
56
|
|
|
@@ -187,11 +192,16 @@ def compute_score(meta: dict, access_count: int = 0, config: dict | None = None)
|
|
|
187
192
|
if archived or status == "dropped":
|
|
188
193
|
return 0.0
|
|
189
194
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
created =
|
|
193
|
-
|
|
194
|
-
created = datetime.
|
|
195
|
+
created_raw = meta.get("created", "")
|
|
196
|
+
if isinstance(created_raw, datetime):
|
|
197
|
+
created = created_raw
|
|
198
|
+
elif isinstance(created_raw, date):
|
|
199
|
+
created = datetime(created_raw.year, created_raw.month, created_raw.day)
|
|
200
|
+
else:
|
|
201
|
+
try:
|
|
202
|
+
created = datetime.fromisoformat(str(created_raw))
|
|
203
|
+
except (ValueError, TypeError):
|
|
204
|
+
created = datetime.now(UTC)
|
|
195
205
|
|
|
196
206
|
tz = UTC if not created.tzinfo else created.tzinfo
|
|
197
207
|
days_old = max(0, (datetime.now(UTC) - created.replace(tzinfo=tz)).days)
|
|
@@ -274,18 +284,19 @@ def _find_file_by_slug(slug: str) -> Path | None:
|
|
|
274
284
|
return None
|
|
275
285
|
|
|
276
286
|
|
|
277
|
-
def _update_frontmatter_field(file_path: Path, field: str, value) ->
|
|
287
|
+
def _update_frontmatter_field(file_path: Path, field: str, value) -> bool:
|
|
278
288
|
text = file_path.read_text(encoding="utf-8")
|
|
279
289
|
parts = text.split("---", 2)
|
|
280
290
|
if len(parts) < 3:
|
|
281
|
-
return
|
|
291
|
+
return False
|
|
282
292
|
try:
|
|
283
293
|
meta = yaml.safe_load(parts[1].strip()) or {}
|
|
284
294
|
except yaml.YAMLError:
|
|
285
|
-
return
|
|
295
|
+
return False
|
|
286
296
|
meta[field] = value
|
|
287
297
|
new_fm = yaml.dump(meta, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
288
298
|
_atomic_write(file_path, f"---\n{new_fm}---\n{parts[2]}")
|
|
299
|
+
return True
|
|
289
300
|
|
|
290
301
|
|
|
291
302
|
def _reinforce_confidence(slug: str) -> None:
|
|
@@ -493,17 +504,16 @@ def pin_page(slug: str) -> bool:
|
|
|
493
504
|
f = _find_file_by_slug(slug)
|
|
494
505
|
if not f:
|
|
495
506
|
return False
|
|
496
|
-
_update_frontmatter_field(f, "pinned", True)
|
|
497
|
-
return True
|
|
507
|
+
return _update_frontmatter_field(f, "pinned", True)
|
|
498
508
|
|
|
499
509
|
|
|
500
510
|
def archive_page(slug: str) -> bool:
|
|
501
511
|
f = _find_file_by_slug(slug)
|
|
502
512
|
if not f:
|
|
503
513
|
return False
|
|
504
|
-
_update_frontmatter_field(f, "status", "dropped")
|
|
505
|
-
_update_frontmatter_field(f, "archived", True)
|
|
506
|
-
return
|
|
514
|
+
dropped = _update_frontmatter_field(f, "status", "dropped")
|
|
515
|
+
archived = _update_frontmatter_field(f, "archived", True)
|
|
516
|
+
return dropped and archived
|
|
507
517
|
|
|
508
518
|
|
|
509
519
|
def list_drafts() -> list[dict]:
|
|
@@ -547,7 +557,12 @@ def promote_draft(
|
|
|
547
557
|
body = meta.get("body", "")
|
|
548
558
|
|
|
549
559
|
final_title = title or meta.get("title", slug)
|
|
550
|
-
|
|
560
|
+
_type_dirs = {
|
|
561
|
+
"concept": "concepts", "concepts": "concepts",
|
|
562
|
+
"strategy": "strategies", "strategies": "strategies",
|
|
563
|
+
"anti-pattern": "anti-patterns", "anti-patterns": "anti-patterns",
|
|
564
|
+
}
|
|
565
|
+
final_type = _type_dirs.get(wiki_type or meta.get("draft_type", "concept"), "concepts")
|
|
551
566
|
final_area = area or meta.get("draft_area", "computing")
|
|
552
567
|
human_note = meta.get("source_note") or None
|
|
553
568
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Build hook: bundle the shareable asset layer during any source build.
|
|
2
|
+
|
|
3
|
+
The repo root is the single source of truth for `.claude/`, `templates/`,
|
|
4
|
+
`_meta/`, `settings/`. When building from a git checkout (pip install ./cli,
|
|
5
|
+
pip install git+...#subdirectory=cli, python -m build), copy them into
|
|
6
|
+
`knowledge_studio/_assets/` before build_py runs, so source installs are
|
|
7
|
+
identical to PyPI wheels. When building from an sdist the repo root is
|
|
8
|
+
absent and `_assets/` is already included via MANIFEST.in — skip silently.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import shutil
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from setuptools import setup
|
|
16
|
+
from setuptools.command.build_py import build_py
|
|
17
|
+
|
|
18
|
+
_MAP = [
|
|
19
|
+
(".claude", "claude"),
|
|
20
|
+
("templates", "templates"),
|
|
21
|
+
("_meta", "_meta"),
|
|
22
|
+
("settings", "settings"),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class build_py_with_assets(build_py):
|
|
27
|
+
def run(self):
|
|
28
|
+
cli_dir = Path(__file__).resolve().parent
|
|
29
|
+
repo_root = cli_dir.parent
|
|
30
|
+
if (repo_root / ".claude").is_dir() and (repo_root / "templates").is_dir():
|
|
31
|
+
dest_root = cli_dir / "knowledge_studio" / "_assets"
|
|
32
|
+
if dest_root.exists():
|
|
33
|
+
shutil.rmtree(dest_root)
|
|
34
|
+
dest_root.mkdir(parents=True)
|
|
35
|
+
for src_name, dest_name in _MAP:
|
|
36
|
+
src = repo_root / src_name
|
|
37
|
+
if src.is_dir():
|
|
38
|
+
shutil.copytree(src, dest_root / dest_name)
|
|
39
|
+
super().run()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
setup(cmdclass={"build_py": build_py_with_assets})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|