ki-manager 2.3.0.dev3__tar.gz → 2.3.1__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.
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/.gitignore +3 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/CHANGELOG.md +28 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/PKG-INFO +1 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/pyproject.toml +1 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/__init__.py +1 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/cli.py +6 -3
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/analyze_module.py +3 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/audit_coverage.py +1 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/check_ki_drift.py +1 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/finalize_ki_scaffolds.py +1 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/find_unmapped_files.py +2 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/generate_ki_scaffolds.py +1 -1
- ki_manager-2.3.1/src/ki_manager/scripts/git_diff_secured.py +81 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/ki_search.py +1 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/ki_utils.py +44 -2
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/knowledge_engine.py +5 -4
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/sync_agents_md.py +8 -4
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/server.py +329 -143
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/tools/facade.py +45 -1
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/tools/scaffold.py +7 -6
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/workflows/expand-knowledge.md +4 -4
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/workflows/sync-knowledge.md +2 -2
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/LICENSE +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/README.md +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/add_ki_to_config.py +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/generate_dir_index.py +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/ki_dependency_analyzer.py +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/scripts/ki_graph_visualize.py +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/templates/KI_template.md +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/tools/__init__.py +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/workflows/create-adr.md +0 -0
- {ki_manager-2.3.0.dev3 → ki_manager-2.3.1}/src/ki_manager/workflows/scaffold-knowledge.md +0 -0
|
@@ -9,7 +9,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
-
## [2.3.
|
|
12
|
+
## [2.3.1] — 2026-10-18
|
|
13
|
+
|
|
14
|
+
### Security & Hardening
|
|
15
|
+
- **Path Traversal Protection**: Enforced strict directory boundary containment across all file discovery and inspection tools (`find_unmapped_files`, `analyze_module`, `ki_search`, and custom agent paths) to prevent prefix-based sandbox traversal attacks.
|
|
16
|
+
- **Secured Git Operations (`git_diff_secured`)**: Added hardened diff inspection tool protecting against flag injection (`--output`, etc.), shell command chaining, and repository boundary escapes.
|
|
17
|
+
- **Restricted File Modifications**: Extended write protection to block direct modifications of shell/executable scripts (`.sh`, `.bash`, `.zsh`, `.js`, `.ts`, `.vbs`, `.wsf`) and internal system state files (`doc_config.json`, `ki_config.json`, `config.json`, `doc_state.json`) via direct file editing tools.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Unified Tool Dispatch Schema**: In compact facade mode, dynamically bind `ki_call` and `ki_mutate` tool validation schemas (`enum` and `description`) directly to tool `readOnlyHint` annotations, eliminating dispatch conflicts and preventing model confusion.
|
|
21
|
+
- **Date Format Resilience in `update_last_verified`**: Extended `update_last_verified` to automatically detect and update all supported date notation variants (HTML comment `<!-- last_verified: YYYY-MM-DD -->`, YAML frontmatter `last_verified: "YYYY-MM-DD"`, and Markdown bold `**Last verified**: YYYY-MM-DD`), and automatically insert the verification tag if missing.
|
|
22
|
+
- **Workflow Instructions Alignment**: Synchronized tool arguments and signatures across `/expand-knowledge` and `/sync-knowledge` instructions, ensuring seamless execution in both compact facade and full tool modes.
|
|
23
|
+
- **Daily MCP Log Rotation**: Server diagnostics now rotate daily (`mcp_YYYY-MM-DD.log`) with payload truncation to prevent runaway disk usage during high-frequency sessions.
|
|
24
|
+
|
|
25
|
+
## [2.3.0] — 2026-10-11
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- **Unified Cascade Path Resolution (`resolve_know_path`)**: `read_know_file`, `write_know_file`, and `edit_know_file` now share `ki_read`'s smart cascade resolver. AI agents can access and edit Knowledge Items using flat basenames (e.g. `KI_data_persistence.md`) without requiring explicit `knowledge/` directory prefixes.
|
|
29
|
+
- **Smart Directory Placement for New KIs**: `write_know_file` now updates existing files in place across `knowledge/` rather than creating duplicates in the sandbox root, and automatically routes newly created flat KI files (`KI_*.md`, `*.ki.md`) into `<jail>/knowledge/`.
|
|
30
|
+
- **ADR Name & Prefix Tolerance**: `resolve_know_path` and `ki_read` gracefully handle variations with or without `ADR_`/`adr_`/`ADR-` prefixes (e.g., resolving `ADR_002_node_status_queue_and_ui_feedback.md` to `002_node_status_queue_and_ui_feedback.md`) as well as numeric ADR IDs.
|
|
31
|
+
- **Accurate Document Listings**: `get_ki_list_table()` dynamically reconciles registered entries in `doc_config.json` against filesystem reality, outputting exact on-disk filenames in document overviews.
|
|
32
|
+
- **Comprehensive Project Overview**: `ki_instructions(document="knowledge-items")`, prompts, and `ki://knowledge-items.md` now present both Knowledge Items and Architecture Decision Records (ADRs) tables together when ADRs exist.
|
|
33
|
+
|
|
34
|
+
### Highlights from 2.3.0 Development Cycle
|
|
35
|
+
- Compact Facade Mode (`--tool-mode compact`) reducing MCP tools footprint from 34 schemas to 6 tools (`ki_instructions`, `ki_search`, `ki_read`, `ki_tools`, `ki_call`, `ki_mutate`), saving thousands of context tokens.
|
|
36
|
+
- Native BM25 search engine (`ki_search`) with bilingual EN/RU stemming and field-weighted ranking.
|
|
37
|
+
- Token-efficient reader with section filtering and character offset pagination (`ki_read`, `read_know_file`).
|
|
38
|
+
- Batch ADR superseding (`create_adr`) with automatic status transitions, warning banners, git/environment snapshots, and quality sections (`rejected_alternatives`, `invariants`, `verification`).
|
|
39
|
+
- Automated drift analysis (`check_ki_drift`) and Python AST pre-enrichment during scaffolding.
|
|
13
40
|
|
|
14
41
|
### Added
|
|
15
42
|
- `create_adr` batch `supersedes` support: accept a list of previous ADR IDs (`supersedes: ["001", "002"]`) to supersede multiple older records simultaneously; automatically updates deprecated ADR files with warning banners and `Superseded by ADR XXX` statuses, updates `doc_config.json`, and embeds bi-directional cross-links in the generated ADR.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ki-manager
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Knowledge Item (KI) management MCP server for AI-assisted development
|
|
5
5
|
Project-URL: Homepage, https://github.com/Laeryid/KI-base
|
|
6
6
|
Project-URL: Repository, https://github.com/Laeryid/KI-base
|
|
@@ -144,11 +144,14 @@ def main():
|
|
|
144
144
|
if command == "install-skills":
|
|
145
145
|
install_skills(sys.argv[2:])
|
|
146
146
|
elif command in ("--version", "-v"):
|
|
147
|
-
import importlib.metadata
|
|
148
147
|
try:
|
|
149
|
-
|
|
148
|
+
from . import __version__ as v
|
|
150
149
|
except Exception:
|
|
151
|
-
|
|
150
|
+
import importlib.metadata
|
|
151
|
+
try:
|
|
152
|
+
v = importlib.metadata.version("ki-manager")
|
|
153
|
+
except Exception:
|
|
154
|
+
v = "unknown"
|
|
152
155
|
print(f"ki-manager {v}")
|
|
153
156
|
sys.exit(0)
|
|
154
157
|
else:
|
|
@@ -32,6 +32,9 @@ def analyze_path(target_path: str, recursive: bool = False):
|
|
|
32
32
|
|
|
33
33
|
# Resolve absolute path
|
|
34
34
|
abs_target = os.path.normpath(os.path.join(project_root, target_path))
|
|
35
|
+
if not ki_utils.is_subpath(abs_target, project_root):
|
|
36
|
+
print(f"Error: Path '{target_path}' escapes project root.")
|
|
37
|
+
return
|
|
35
38
|
|
|
36
39
|
if not os.path.exists(abs_target):
|
|
37
40
|
print(f"Error: Path {target_path} not found.")
|
|
@@ -65,7 +65,7 @@ def load_ki_files() -> list:
|
|
|
65
65
|
ki_dir = os.path.join(get_knowledge_root(), "knowledge")
|
|
66
66
|
if not os.path.isdir(ki_dir):
|
|
67
67
|
return []
|
|
68
|
-
return [f for f in os.listdir(ki_dir) if
|
|
68
|
+
return [f for f in os.listdir(ki_dir) if ki_utils.is_ki_filename(f)]
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
# ─── Metrics ──────────────────────────────────────────────────────────────────
|
|
@@ -286,7 +286,7 @@ def run_drift_check(
|
|
|
286
286
|
return [check_single_ki(target_name, target_path, doc_config, project_root, knowledge_dir)]
|
|
287
287
|
|
|
288
288
|
results = []
|
|
289
|
-
for f in sorted(knowledge_dir.glob("
|
|
289
|
+
for f in sorted(f for f in knowledge_dir.glob("*.md") if ki_utils.is_ki_filename(f.name)):
|
|
290
290
|
results.append(check_single_ki(f.name, f, doc_config, project_root, knowledge_dir))
|
|
291
291
|
|
|
292
292
|
return results
|
|
@@ -70,7 +70,7 @@ def finalize_scaffolds(dry_run: bool = False) -> None:
|
|
|
70
70
|
skipped_clean = 0
|
|
71
71
|
|
|
72
72
|
for fname in sorted(os.listdir(ki_dir)):
|
|
73
|
-
if not
|
|
73
|
+
if not ki_utils.is_ki_filename(fname):
|
|
74
74
|
continue
|
|
75
75
|
|
|
76
76
|
ki_path = os.path.join(ki_dir, fname)
|
|
@@ -29,6 +29,8 @@ def get_unmapped_files(target_path: str = ".") -> List[str]:
|
|
|
29
29
|
|
|
30
30
|
# 2. Scan the target directory
|
|
31
31
|
abs_target = os.path.abspath(os.path.join(project_root, target_path))
|
|
32
|
+
if not ki_utils.is_subpath(abs_target, project_root):
|
|
33
|
+
return []
|
|
32
34
|
if not os.path.exists(abs_target):
|
|
33
35
|
return []
|
|
34
36
|
|
|
@@ -514,7 +514,7 @@ def print_scaffold_status() -> None:
|
|
|
514
514
|
enriched_count = 0
|
|
515
515
|
|
|
516
516
|
for fname in sorted(os.listdir(ki_dir)):
|
|
517
|
-
if not
|
|
517
|
+
if not ki_utils.is_ki_filename(fname):
|
|
518
518
|
continue
|
|
519
519
|
|
|
520
520
|
ki_path = os.path.join(ki_dir, fname)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
git_diff_secured.py
|
|
3
|
+
|
|
4
|
+
Safely runs git status and git diff for project files within the project sandbox.
|
|
5
|
+
Prevents argument/flag injection and path traversal.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import argparse
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
16
|
+
import ki_utils
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def git_diff_secured(paths: List[str] = None) -> str:
|
|
20
|
+
project_root = ki_utils.get_project_root()
|
|
21
|
+
if not project_root or not os.path.exists(project_root):
|
|
22
|
+
return "Error: No active project context."
|
|
23
|
+
|
|
24
|
+
safe_paths = []
|
|
25
|
+
if paths:
|
|
26
|
+
for p in paths:
|
|
27
|
+
clean_p = p.strip()
|
|
28
|
+
if not clean_p:
|
|
29
|
+
continue
|
|
30
|
+
if clean_p.startswith("-") or any(char in clean_p for char in [";", "|", "&", ">", "<", "`"]):
|
|
31
|
+
return f"Error: Suspicious argument or path '{clean_p}'."
|
|
32
|
+
abs_p = os.path.abspath(os.path.join(project_root, clean_p))
|
|
33
|
+
if not ki_utils.is_subpath(abs_p, project_root):
|
|
34
|
+
return f"Access Denied: Path '{clean_p}' escapes project root."
|
|
35
|
+
safe_paths.append(os.path.relpath(abs_p, project_root).replace("\\", "/"))
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if safe_paths:
|
|
39
|
+
stat_cmd = ["git", "status", "--short", "--"] + safe_paths
|
|
40
|
+
diff_cmd = ["git", "diff", "HEAD", "--"] + safe_paths
|
|
41
|
+
else:
|
|
42
|
+
stat_cmd = ["git", "status", "--short"]
|
|
43
|
+
diff_cmd = ["git", "diff", "HEAD"]
|
|
44
|
+
|
|
45
|
+
stat_res = subprocess.run(
|
|
46
|
+
stat_cmd, cwd=project_root, capture_output=True, text=True, encoding="utf-8"
|
|
47
|
+
)
|
|
48
|
+
diff_res = subprocess.run(
|
|
49
|
+
diff_cmd, cwd=project_root, capture_output=True, text=True, encoding="utf-8"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
output = []
|
|
53
|
+
if stat_res.stdout.strip():
|
|
54
|
+
output.append("### Git Status:\n" + stat_res.stdout.strip())
|
|
55
|
+
if diff_res.stdout.strip():
|
|
56
|
+
output.append("### Git Diff:\n" + diff_res.stdout.strip())
|
|
57
|
+
if not output:
|
|
58
|
+
output.append("No changes detected in git.")
|
|
59
|
+
|
|
60
|
+
return "\n\n".join(output)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return f"Git execution error: {str(e)}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main():
|
|
66
|
+
if sys.platform == "win32":
|
|
67
|
+
try:
|
|
68
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
parser = argparse.ArgumentParser(description="Secured git diff for project files.")
|
|
73
|
+
parser.add_argument("paths", nargs=argparse.REMAINDER, help="File or directory paths relative to project root")
|
|
74
|
+
args = parser.parse_args()
|
|
75
|
+
|
|
76
|
+
result = git_diff_secured(args.paths)
|
|
77
|
+
print(result)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
main()
|
|
@@ -218,7 +218,7 @@ def search_knowledge(
|
|
|
218
218
|
return {"error": "No project root found. Run ki_init_project or specify workspace.", "results": []}
|
|
219
219
|
|
|
220
220
|
knowledge_root = ki_utils.get_knowledge_root()
|
|
221
|
-
if not knowledge_root or not os.path.exists(knowledge_root) or not
|
|
221
|
+
if not knowledge_root or not os.path.exists(knowledge_root) or not ki_utils.is_subpath(knowledge_root, project_root):
|
|
222
222
|
for cand in [
|
|
223
223
|
os.path.join(project_root, ".ki-base", "knowledge"),
|
|
224
224
|
os.path.join(project_root, "knowledge"),
|
|
@@ -58,6 +58,28 @@ def normalize_path(path_str: str, make_absolute: bool = True) -> str:
|
|
|
58
58
|
return path_str
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def is_subpath(target: str, root: str) -> bool:
|
|
62
|
+
"""Checks whether target path is strictly within or equal to root directory."""
|
|
63
|
+
if not target or not root:
|
|
64
|
+
return False
|
|
65
|
+
try:
|
|
66
|
+
norm_t = os.path.normcase(os.path.abspath(target))
|
|
67
|
+
norm_r = os.path.normcase(os.path.abspath(root))
|
|
68
|
+
return os.path.commonpath([norm_t, norm_r]) == norm_r
|
|
69
|
+
except (ValueError, OSError):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_ki_filename(name: str, include_overview: bool = False) -> bool:
|
|
74
|
+
"""Checks if a file name matches Knowledge Item convention (KI_*.md or *.ki.md)."""
|
|
75
|
+
if not name or not name.endswith(".md"):
|
|
76
|
+
return False
|
|
77
|
+
base = os.path.basename(name)
|
|
78
|
+
if not include_overview and base in ("_OVERVIEW.ki.md", "_OVERVIEW.md"):
|
|
79
|
+
return False
|
|
80
|
+
return base.startswith("KI_") or base.endswith(".ki.md")
|
|
81
|
+
|
|
82
|
+
|
|
61
83
|
# ─── Registry Management ─────────────────────────────────────────────────────
|
|
62
84
|
|
|
63
85
|
def get_registry_path() -> Path:
|
|
@@ -139,7 +161,7 @@ def find_project_by_cwd(cwd=None) -> dict:
|
|
|
139
161
|
|
|
140
162
|
for proj_root, data in registry["projects"].items():
|
|
141
163
|
norm_proj = normalize_path(proj_root)
|
|
142
|
-
if
|
|
164
|
+
if is_subpath(cwd, norm_proj):
|
|
143
165
|
if len(norm_proj) > max_len:
|
|
144
166
|
max_len = len(norm_proj)
|
|
145
167
|
best_match = data
|
|
@@ -409,10 +431,30 @@ def get_ki_list_table() -> str:
|
|
|
409
431
|
items = doc_config.get("knowledge_items", {})
|
|
410
432
|
if not items:
|
|
411
433
|
return "No Knowledge Items registered yet."
|
|
434
|
+
jail = get_knowledge_root()
|
|
435
|
+
proj = get_project_root()
|
|
436
|
+
dec_dirs = get_decisions_dirs(proj, jail)
|
|
437
|
+
|
|
412
438
|
rows = ["| File | Topic / Summary |", "|------|-----------------|"]
|
|
413
439
|
for name, info in sorted(items.items()):
|
|
414
440
|
summary = info.get("summary", info.get("description", "—"))
|
|
415
|
-
|
|
441
|
+
display_name = name
|
|
442
|
+
if "path" in info and info["path"]:
|
|
443
|
+
display_name = info["path"]
|
|
444
|
+
elif jail or proj:
|
|
445
|
+
# Check if name exists literally
|
|
446
|
+
exists_literal = False
|
|
447
|
+
for root_cand in ([jail] if jail else []) + dec_dirs:
|
|
448
|
+
if os.path.exists(os.path.join(root_cand, name)) or os.path.exists(os.path.join(root_cand, "knowledge", name)):
|
|
449
|
+
exists_literal = True
|
|
450
|
+
break
|
|
451
|
+
if not exists_literal and re.match(r"^(?:ADR|adr)[_\-]", name):
|
|
452
|
+
stripped = re.sub(r"^(?:ADR|adr)[_\-]", "", name)
|
|
453
|
+
for d in dec_dirs:
|
|
454
|
+
if os.path.exists(os.path.join(d, stripped)):
|
|
455
|
+
display_name = stripped
|
|
456
|
+
break
|
|
457
|
+
rows.append(f"| `{display_name}` | {summary} |")
|
|
416
458
|
return "\n".join(rows)
|
|
417
459
|
|
|
418
460
|
|
|
@@ -201,11 +201,11 @@ class KnowledgeEngine:
|
|
|
201
201
|
if not knowledge_dir.exists():
|
|
202
202
|
return "Knowledge directory not found"
|
|
203
203
|
|
|
204
|
-
path_pattern = re.compile(r"`(
|
|
204
|
+
path_pattern = re.compile(r"`([a-zA-Z0-9_\-\.]+/[a-zA-Z0-9_\-\./]+)`")
|
|
205
205
|
|
|
206
206
|
updated_count = 0
|
|
207
207
|
for filename in os.listdir(knowledge_dir):
|
|
208
|
-
if not
|
|
208
|
+
if not ki_utils.is_ki_filename(filename, include_overview=True):
|
|
209
209
|
continue
|
|
210
210
|
|
|
211
211
|
ki_path = knowledge_dir / filename
|
|
@@ -215,8 +215,9 @@ class KnowledgeEngine:
|
|
|
215
215
|
mentioned = path_pattern.findall(content)
|
|
216
216
|
valid_paths = set()
|
|
217
217
|
for p in mentioned:
|
|
218
|
-
p = p.rstrip("
|
|
219
|
-
|
|
218
|
+
p = p.rstrip(".,;:)")
|
|
219
|
+
target_file = (self.project_root / p).resolve()
|
|
220
|
+
if ki_utils.is_subpath(str(target_file), str(self.project_root)) and target_file.exists():
|
|
220
221
|
valid_paths.add(p.replace("\\", "/"))
|
|
221
222
|
|
|
222
223
|
if filename not in ki_items:
|
|
@@ -16,12 +16,16 @@ import ki_utils
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def resolve_agents_path(agents_path: str = None) -> str:
|
|
19
|
-
"""Finds the AGENTS.md file in the project."""
|
|
20
19
|
if agents_path:
|
|
21
20
|
p = ki_utils.normalize_path(agents_path)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
pr = ki_utils.get_project_root()
|
|
22
|
+
kr = ki_utils.get_knowledge_root()
|
|
23
|
+
allowed = [pr] if pr else []
|
|
24
|
+
if kr and kr not in allowed:
|
|
25
|
+
allowed.append(kr)
|
|
26
|
+
if allowed and not any(ki_utils.is_subpath(p, r) for r in allowed):
|
|
27
|
+
return ""
|
|
28
|
+
return p
|
|
25
29
|
|
|
26
30
|
pr = ki_utils.get_project_root()
|
|
27
31
|
kr = ki_utils.get_knowledge_root()
|