ki-manager 2.3.1.dev1__tar.gz → 2.3.2.dev2__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.1.dev1 → ki_manager-2.3.2.dev2}/CHANGELOG.md +23 -1
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/PKG-INFO +1 -1
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/pyproject.toml +1 -1
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/__init__.py +1 -1
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/cli.py +6 -3
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/check_ki_drift.py +70 -14
- ki_manager-2.3.2.dev2/src/ki_manager/scripts/find_unmapped_files.py +143 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/ki_search.py +32 -5
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/ki_utils.py +187 -23
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/server.py +57 -9
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/tools/facade.py +43 -1
- ki_manager-2.3.2.dev2/src/ki_manager/workflows/agent-rules.md +152 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/workflows/expand-knowledge.md +4 -2
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/workflows/scaffold-knowledge.md +4 -0
- ki_manager-2.3.1.dev1/src/ki_manager/scripts/find_unmapped_files.py +0 -101
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/.gitignore +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/LICENSE +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/README.md +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/add_ki_to_config.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/analyze_module.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/audit_coverage.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/finalize_ki_scaffolds.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/generate_dir_index.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/generate_ki_scaffolds.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/git_diff_secured.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/ki_dependency_analyzer.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/ki_graph_visualize.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/knowledge_engine.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/sync_agents_md.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/templates/KI_template.md +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/tools/__init__.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/tools/scaffold.py +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/workflows/create-adr.md +0 -0
- {ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/workflows/sync-knowledge.md +0 -0
|
@@ -9,7 +9,29 @@ 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.2.dev2] — 2026-11-01
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`ki_list` Navigation Tool**:
|
|
16
|
+
- Direct navigation tool to list registered Knowledge Items and Architecture Decision Records (ADRs) with summaries and covered modules without filesystem scans.
|
|
17
|
+
- Supports filtering by type (`scope: "all" | "ki" | "adr"`), output formats (`format: "markdown" | "json"`), and search query (`filter: "<text>"`).
|
|
18
|
+
- Available as a direct top-level tool in both full mode (35 tools) and compact facade mode (7 tools).
|
|
19
|
+
- **BM25 Search Pagination (`ki_search`)**:
|
|
20
|
+
- Added 1-based `page` parameter for intuitive result pagination alongside `offset`.
|
|
21
|
+
- Prominent pagination header and next-page action hints (`[Page X/Y]`, `Next page: ki_search(query="...", page=X+1)`) in search outputs to prevent AI models from getting stuck in repeat queries.
|
|
22
|
+
|
|
23
|
+
## [2.3.2.dev1] — 2026-10-20
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **`find_unmapped_files` Traversal & Scoping**:
|
|
27
|
+
- Automatically loads and enforces `.gitignore` patterns and `doc_config.json` `exclude` rules (in addition to `exclude_patterns`) during repository discovery.
|
|
28
|
+
- When scanning repository root by default, scopes file discovery to root segments of configured `tracked_modules`, avoiding timeouts and runaway directory walks across large external data trees.
|
|
29
|
+
- Made the CLI `path` argument optional, defaulting to root / tracked modules.
|
|
30
|
+
- **`check_ki_drift` Markdown Table Header Validation**:
|
|
31
|
+
- Validates Markdown table headers explicitly for file path columns (`File`, `Path`, `Source`, `Location`) before inspecting cells for broken paths.
|
|
32
|
+
- Tables without file columns (e.g. `| Tool | Access | Purpose |`) are cleanly bypassed, eliminating false-positive `Missing file: Access` errors.
|
|
33
|
+
|
|
34
|
+
## [2.3.1] — 2026-10-18
|
|
13
35
|
|
|
14
36
|
### Security & Hardening
|
|
15
37
|
- **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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ki-manager
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2.dev2
|
|
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:
|
|
@@ -76,31 +76,87 @@ def parse_ki_file(ki_path: Path) -> Dict[str, Any]:
|
|
|
76
76
|
|
|
77
77
|
# Extract Key Components table symbols
|
|
78
78
|
# Expected table format: | `SymbolName` | `rel/path/file.ext` | Purpose |
|
|
79
|
+
# Only tables that explicitly contain a File/Path column header are inspected.
|
|
79
80
|
table_symbols: List[Dict[str, str]] = []
|
|
80
81
|
in_key_components = False
|
|
81
|
-
|
|
82
|
+
pending_header: Optional[List[str]] = None
|
|
83
|
+
current_table_active = False
|
|
84
|
+
sym_idx = -1
|
|
85
|
+
file_idx = -1
|
|
82
86
|
|
|
83
87
|
for line in lines:
|
|
84
88
|
if line.startswith("## Key Components"):
|
|
85
89
|
in_key_components = True
|
|
90
|
+
pending_header = None
|
|
91
|
+
current_table_active = False
|
|
86
92
|
continue
|
|
87
93
|
elif in_key_components and line.startswith("## "):
|
|
88
94
|
in_key_components = False
|
|
95
|
+
pending_header = None
|
|
96
|
+
current_table_active = False
|
|
89
97
|
continue
|
|
90
98
|
|
|
91
|
-
if in_key_components
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
if not in_key_components:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
stripped = line.strip()
|
|
103
|
+
if not stripped.startswith("|"):
|
|
104
|
+
pending_header = None
|
|
105
|
+
current_table_active = False
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
raw_cells = [c.strip() for c in stripped.strip("|").split("|")]
|
|
109
|
+
# Check if separator row (e.g. |---|---| or |:---|---:|)
|
|
110
|
+
is_separator = (
|
|
111
|
+
len(raw_cells) > 0
|
|
112
|
+
and all(re.match(r"^:?-{2,}:?$", c) for c in raw_cells if c)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if is_separator:
|
|
116
|
+
if pending_header:
|
|
117
|
+
file_idx = -1
|
|
118
|
+
for i, col in enumerate(pending_header):
|
|
119
|
+
col_lower = col.lower()
|
|
120
|
+
if any(w in col_lower for w in ["file", "path", "source", "location"]):
|
|
121
|
+
file_idx = i
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
sym_idx = -1
|
|
125
|
+
for i, col in enumerate(pending_header):
|
|
126
|
+
if i == file_idx:
|
|
127
|
+
continue
|
|
128
|
+
col_lower = col.lower()
|
|
129
|
+
if any(w in col_lower for w in ["class", "function", "symbol", "component", "name", "element", "method", "tool"]):
|
|
130
|
+
sym_idx = i
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
if sym_idx == -1 and file_idx != -1:
|
|
134
|
+
sym_idx = 0 if file_idx != 0 else 1
|
|
135
|
+
|
|
136
|
+
current_table_active = (file_idx != -1 and sym_idx != -1)
|
|
137
|
+
pending_header = None
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
if pending_header is None and not current_table_active:
|
|
141
|
+
# Possible header row
|
|
142
|
+
pending_header = raw_cells
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
if current_table_active:
|
|
146
|
+
if len(raw_cells) > max(sym_idx, file_idx):
|
|
147
|
+
sym_val = raw_cells[sym_idx].strip(" `").strip()
|
|
148
|
+
file_val = raw_cells[file_idx].strip(" `").strip()
|
|
149
|
+
header_keywords = {"component", "file", "path", "purpose", "class", "function", "description", "tool", "access"}
|
|
150
|
+
if (
|
|
151
|
+
sym_val
|
|
152
|
+
and file_val
|
|
153
|
+
and not sym_val.startswith("—")
|
|
154
|
+
and not file_val.startswith("—")
|
|
155
|
+
and not file_val.startswith("-")
|
|
156
|
+
and sym_val.lower() not in header_keywords
|
|
157
|
+
and file_val.lower() not in header_keywords
|
|
158
|
+
):
|
|
159
|
+
table_symbols.append({"symbol": sym_val, "file": file_val})
|
|
104
160
|
|
|
105
161
|
# Extract wiki-links [[KI_*.md]]
|
|
106
162
|
wiki_links = []
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
# Add path to ki_utils
|
|
9
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
10
|
+
import ki_utils
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _scan_path(target_path: str, project_root: str, mapped_paths: set, exclude_patterns: set, ignored_exts: set) -> List[str]:
|
|
14
|
+
abs_target = os.path.abspath(os.path.join(project_root, target_path))
|
|
15
|
+
if not ki_utils.is_subpath(abs_target, project_root) or not os.path.exists(abs_target):
|
|
16
|
+
return []
|
|
17
|
+
|
|
18
|
+
unmapped = []
|
|
19
|
+
if os.path.isfile(abs_target):
|
|
20
|
+
rel_to_project = os.path.relpath(abs_target, project_root).replace("\\", "/")
|
|
21
|
+
fname = os.path.basename(abs_target)
|
|
22
|
+
if fname.startswith(".") or any(fname.endswith(ext) for ext in ignored_exts):
|
|
23
|
+
return []
|
|
24
|
+
if ki_utils.should_exclude(fname, rel_to_project, exclude_patterns):
|
|
25
|
+
return []
|
|
26
|
+
if abs_target in mapped_paths:
|
|
27
|
+
return []
|
|
28
|
+
for mapped in mapped_paths:
|
|
29
|
+
if os.path.isdir(mapped) and abs_target.startswith(os.path.join(mapped, "")):
|
|
30
|
+
return []
|
|
31
|
+
return [rel_to_project]
|
|
32
|
+
|
|
33
|
+
for root, dirs, files in os.walk(abs_target):
|
|
34
|
+
dirs[:] = [
|
|
35
|
+
d for d in dirs
|
|
36
|
+
if not d.startswith(".")
|
|
37
|
+
and not ki_utils.should_exclude(d, os.path.relpath(os.path.join(root, d), project_root), exclude_patterns)
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
for f in files:
|
|
41
|
+
if f.startswith(".") or any(f.endswith(ext) for ext in ignored_exts):
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
full_path = os.path.abspath(os.path.join(root, f))
|
|
45
|
+
rel_to_project = os.path.relpath(full_path, project_root).replace("\\", "/")
|
|
46
|
+
if ki_utils.should_exclude(f, rel_to_project, exclude_patterns):
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
if full_path in mapped_paths:
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
is_parent_mapped = False
|
|
53
|
+
for mapped in mapped_paths:
|
|
54
|
+
if os.path.isdir(mapped):
|
|
55
|
+
if full_path.startswith(os.path.join(mapped, "")):
|
|
56
|
+
is_parent_mapped = True
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
if not is_parent_mapped:
|
|
60
|
+
unmapped.append(rel_to_project)
|
|
61
|
+
|
|
62
|
+
return unmapped
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_unmapped_files(target_path: str = ".") -> List[str]:
|
|
66
|
+
"""Returns a sorted list of unmapped files relative to project root."""
|
|
67
|
+
project_root = ki_utils.get_project_root()
|
|
68
|
+
doc_config = ki_utils.get_doc_config()
|
|
69
|
+
|
|
70
|
+
if not project_root or not doc_config:
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
# 1. Collect all files that are already mapped in doc_config.json
|
|
74
|
+
mapped_paths = set()
|
|
75
|
+
knowledge_items = doc_config.get("knowledge_items", {})
|
|
76
|
+
|
|
77
|
+
for ki in knowledge_items.values():
|
|
78
|
+
depends_on = ki.get("depends_on", [])
|
|
79
|
+
for p in depends_on:
|
|
80
|
+
abs_p = os.path.abspath(os.path.join(project_root, p))
|
|
81
|
+
mapped_paths.add(abs_p)
|
|
82
|
+
|
|
83
|
+
ignored_exts = {'.pyc', '.pyo', '.pyd', '.obj', '.dll', '.exe', '.bin'}
|
|
84
|
+
exclude_patterns = ki_utils.get_exclude_patterns()
|
|
85
|
+
|
|
86
|
+
norm_target = (target_path or ".").strip()
|
|
87
|
+
|
|
88
|
+
# If target is root/empty, scope to tracked_modules if defined
|
|
89
|
+
if norm_target in (".", ""):
|
|
90
|
+
tracked_modules = doc_config.get("coverage_settings", {}).get("tracked_modules") or doc_config.get("tracked_modules", [])
|
|
91
|
+
target_dirs = set()
|
|
92
|
+
if isinstance(tracked_modules, list) and tracked_modules:
|
|
93
|
+
for m in tracked_modules:
|
|
94
|
+
mod_path = m[0] if isinstance(m, (list, tuple)) else m
|
|
95
|
+
if isinstance(mod_path, str) and mod_path.strip():
|
|
96
|
+
norm_p = mod_path.replace("\\", "/").strip("/")
|
|
97
|
+
top_segment = norm_p.split("/")[0] if "/" in norm_p else norm_p
|
|
98
|
+
if top_segment:
|
|
99
|
+
target_dirs.add(top_segment)
|
|
100
|
+
|
|
101
|
+
if target_dirs:
|
|
102
|
+
unmapped_set = set()
|
|
103
|
+
for td in sorted(target_dirs):
|
|
104
|
+
unmapped_set.update(_scan_path(td, project_root, mapped_paths, exclude_patterns, ignored_exts))
|
|
105
|
+
return sorted(unmapped_set)
|
|
106
|
+
else:
|
|
107
|
+
return sorted(_scan_path(".", project_root, mapped_paths, exclude_patterns, ignored_exts))
|
|
108
|
+
|
|
109
|
+
return sorted(_scan_path(norm_target, project_root, mapped_paths, exclude_patterns, ignored_exts))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def find_unmapped_files(target_path: str = "."):
|
|
113
|
+
project_root = ki_utils.get_project_root()
|
|
114
|
+
doc_config = ki_utils.get_doc_config()
|
|
115
|
+
|
|
116
|
+
if not project_root or not doc_config:
|
|
117
|
+
print("Error: Could not resolve project root or doc_config.json")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
display_target = (target_path or ".").strip()
|
|
121
|
+
if display_target != ".":
|
|
122
|
+
abs_target = os.path.abspath(os.path.join(project_root, display_target))
|
|
123
|
+
if not os.path.exists(abs_target):
|
|
124
|
+
print(f"Error: Target path '{display_target}' does not exist.")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
unmapped = get_unmapped_files(display_target)
|
|
128
|
+
if unmapped:
|
|
129
|
+
print(f"### Unmapped Files in '{display_target}':")
|
|
130
|
+
for f in unmapped:
|
|
131
|
+
print(f"- {f}")
|
|
132
|
+
else:
|
|
133
|
+
print(f"All files in '{display_target}' are already mapped in doc_config.json.")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
if sys.platform == "win32":
|
|
138
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
139
|
+
parser = argparse.ArgumentParser(description="Find files in a directory not covered by any KI in doc_config.json")
|
|
140
|
+
parser.add_argument("path", nargs="?", default=".", help="Relative path from project root to scan (default: tracked_modules or .)")
|
|
141
|
+
args = parser.parse_args()
|
|
142
|
+
|
|
143
|
+
find_unmapped_files(args.path)
|
|
@@ -198,6 +198,7 @@ def search_knowledge(
|
|
|
198
198
|
scope: str = "all",
|
|
199
199
|
limit: int = 5,
|
|
200
200
|
offset: int = 0,
|
|
201
|
+
page: Optional[int] = None,
|
|
201
202
|
k1: float = 1.5,
|
|
202
203
|
b: float = 0.75,
|
|
203
204
|
) -> Dict[str, Any]:
|
|
@@ -209,6 +210,7 @@ def search_knowledge(
|
|
|
209
210
|
:param scope: "all", "ki", or "adr".
|
|
210
211
|
:param limit: Max number of results to return (default: 5).
|
|
211
212
|
:param offset: Offset index for pagination (default: 0).
|
|
213
|
+
:param page: 1-based page number for pagination (calculates offset if provided).
|
|
212
214
|
:param k1: BM25 k1 parameter.
|
|
213
215
|
:param b: BM25 b parameter.
|
|
214
216
|
"""
|
|
@@ -217,6 +219,14 @@ def search_knowledge(
|
|
|
217
219
|
if not project_root:
|
|
218
220
|
return {"error": "No project root found. Run ki_init_project or specify workspace.", "results": []}
|
|
219
221
|
|
|
222
|
+
if page is not None:
|
|
223
|
+
try:
|
|
224
|
+
p = int(page)
|
|
225
|
+
if p > 0:
|
|
226
|
+
offset = (p - 1) * limit
|
|
227
|
+
except (ValueError, TypeError):
|
|
228
|
+
pass
|
|
229
|
+
|
|
220
230
|
knowledge_root = ki_utils.get_knowledge_root()
|
|
221
231
|
if not knowledge_root or not os.path.exists(knowledge_root) or not ki_utils.is_subpath(knowledge_root, project_root):
|
|
222
232
|
for cand in [
|
|
@@ -231,7 +241,7 @@ def search_knowledge(
|
|
|
231
241
|
|
|
232
242
|
q_tokens = tokenize(query)
|
|
233
243
|
if not q_tokens:
|
|
234
|
-
return {"query": query, "total": 0, "results": []}
|
|
244
|
+
return {"query": query, "total": 0, "offset": offset, "limit": limit, "page": 1, "total_pages": 1, "results": []}
|
|
235
245
|
|
|
236
246
|
docs: List[Dict[str, Any]] = []
|
|
237
247
|
|
|
@@ -261,7 +271,7 @@ def search_knowledge(
|
|
|
261
271
|
|
|
262
272
|
total_docs = len(docs)
|
|
263
273
|
if total_docs == 0:
|
|
264
|
-
return {"query": query, "total": 0, "results": []}
|
|
274
|
+
return {"query": query, "total": 0, "offset": offset, "limit": limit, "page": 1, "total_pages": 1, "results": []}
|
|
265
275
|
|
|
266
276
|
avg_dl = sum(d["weighted_len"] for d in docs) / total_docs
|
|
267
277
|
|
|
@@ -298,14 +308,19 @@ def search_knowledge(
|
|
|
298
308
|
})
|
|
299
309
|
|
|
300
310
|
scored_results.sort(key=lambda x: x["score"], reverse=True)
|
|
311
|
+
total_count = len(scored_results)
|
|
301
312
|
safe_offset = max(0, offset)
|
|
302
313
|
final_results = scored_results[safe_offset : safe_offset + limit]
|
|
314
|
+
current_page = (safe_offset // limit) + 1 if limit > 0 else 1
|
|
315
|
+
total_pages = max(1, math.ceil(total_count / limit)) if limit > 0 else 1
|
|
303
316
|
|
|
304
317
|
return {
|
|
305
318
|
"query": query,
|
|
306
|
-
"total":
|
|
319
|
+
"total": total_count,
|
|
307
320
|
"offset": safe_offset,
|
|
308
321
|
"limit": limit,
|
|
322
|
+
"page": current_page,
|
|
323
|
+
"total_pages": total_pages,
|
|
309
324
|
"results": final_results,
|
|
310
325
|
}
|
|
311
326
|
|
|
@@ -317,13 +332,24 @@ def format_search_markdown(search_dict: Dict[str, Any]) -> str:
|
|
|
317
332
|
results = search_dict.get("results", [])
|
|
318
333
|
offset = search_dict.get("offset", 0)
|
|
319
334
|
limit = search_dict.get("limit", 5)
|
|
335
|
+
page = search_dict.get("page")
|
|
336
|
+
total_pages = search_dict.get("total_pages")
|
|
320
337
|
|
|
321
338
|
if total == 0 or not results:
|
|
322
339
|
return f'No documentation matching "{query}" found.'
|
|
323
340
|
|
|
341
|
+
if page is None and limit > 0:
|
|
342
|
+
page = (offset // limit) + 1
|
|
343
|
+
if total_pages is None and limit > 0:
|
|
344
|
+
total_pages = max(1, math.ceil(total / limit))
|
|
345
|
+
|
|
324
346
|
start_idx = offset + 1
|
|
325
347
|
end_idx = offset + len(results)
|
|
326
|
-
out = [f'Found {total} result(s) for "{query}" (showing {start_idx}-{end_idx}):\n']
|
|
348
|
+
out = [f'Found {total} result(s) for "{query}" [Page {page}/{total_pages} (showing {start_idx}-{end_idx})]:\n']
|
|
349
|
+
|
|
350
|
+
if end_idx < total:
|
|
351
|
+
next_page = (page or 1) + 1
|
|
352
|
+
out.append(f'> 💡 **Next page**: `ki_search(query="{query}", page={next_page})` (or `offset={end_idx}`)\n')
|
|
327
353
|
|
|
328
354
|
for i, r in enumerate(results, start=start_idx):
|
|
329
355
|
doc_type = r.get("type", "ki").upper()
|
|
@@ -337,6 +363,7 @@ def format_search_markdown(search_dict: Dict[str, Any]) -> str:
|
|
|
337
363
|
out.append(f" > {snippet}")
|
|
338
364
|
|
|
339
365
|
if end_idx < total:
|
|
340
|
-
|
|
366
|
+
next_page = (page or 1) + 1
|
|
367
|
+
out.append(f"\n*Showing {end_idx} of {total} results. Call `ki_search(query='{query}', page={next_page})` (or `offset={end_idx}`) to view more.*")
|
|
341
368
|
|
|
342
369
|
return "\n".join(out)
|
|
@@ -18,6 +18,7 @@ import tempfile
|
|
|
18
18
|
import argparse
|
|
19
19
|
import fnmatch
|
|
20
20
|
from pathlib import Path
|
|
21
|
+
from typing import Optional, Dict, Any, List, Union
|
|
21
22
|
|
|
22
23
|
# Global state — set by MCP server on initialize
|
|
23
24
|
ACTIVE_WORKSPACE_PATH = None
|
|
@@ -304,15 +305,20 @@ def get_project_root() -> str:
|
|
|
304
305
|
|
|
305
306
|
def get_doc_config_path() -> str:
|
|
306
307
|
root = get_knowledge_root()
|
|
307
|
-
|
|
308
|
+
cand = os.path.join(root, "doc_config.json") if root else ""
|
|
309
|
+
if cand and os.path.exists(cand):
|
|
310
|
+
return cand
|
|
311
|
+
proj = get_project_root()
|
|
312
|
+
if proj:
|
|
313
|
+
for p in [os.path.join(proj, ".ki-base", "doc_config.json"), os.path.join(proj, "doc_config.json")]:
|
|
314
|
+
if os.path.exists(p):
|
|
315
|
+
return p
|
|
316
|
+
return cand
|
|
308
317
|
|
|
309
318
|
|
|
310
319
|
def get_doc_config() -> dict:
|
|
311
|
-
|
|
312
|
-
if
|
|
313
|
-
return {}
|
|
314
|
-
path = os.path.join(root, "doc_config.json")
|
|
315
|
-
if os.path.exists(path):
|
|
320
|
+
path = get_doc_config_path()
|
|
321
|
+
if path and os.path.exists(path):
|
|
316
322
|
try:
|
|
317
323
|
with open(path, "r", encoding="utf-8") as f:
|
|
318
324
|
return json.load(f)
|
|
@@ -335,6 +341,34 @@ def get_local_config() -> dict:
|
|
|
335
341
|
return {}
|
|
336
342
|
|
|
337
343
|
|
|
344
|
+
def load_gitignore_patterns(project_root: str = None) -> set:
|
|
345
|
+
"""Parses .gitignore file in project_root if present, returning a set of pattern strings."""
|
|
346
|
+
if not project_root:
|
|
347
|
+
project_root = get_project_root()
|
|
348
|
+
if not project_root or not os.path.isdir(project_root):
|
|
349
|
+
return set()
|
|
350
|
+
|
|
351
|
+
gitignore_path = os.path.join(project_root, ".gitignore")
|
|
352
|
+
if not os.path.isfile(gitignore_path):
|
|
353
|
+
return set()
|
|
354
|
+
|
|
355
|
+
patterns = set()
|
|
356
|
+
try:
|
|
357
|
+
with open(gitignore_path, "r", encoding="utf-8", errors="replace") as f:
|
|
358
|
+
for line in f:
|
|
359
|
+
line = line.strip()
|
|
360
|
+
if not line or line.startswith("#") or line.startswith("!"):
|
|
361
|
+
continue
|
|
362
|
+
norm = line.replace("\\", "/").rstrip("/")
|
|
363
|
+
if norm.startswith("/"):
|
|
364
|
+
norm = norm.lstrip("/")
|
|
365
|
+
if norm:
|
|
366
|
+
patterns.add(norm)
|
|
367
|
+
except Exception:
|
|
368
|
+
pass
|
|
369
|
+
return patterns
|
|
370
|
+
|
|
371
|
+
|
|
338
372
|
DEFAULT_EXCLUDED_DIRS = {
|
|
339
373
|
".git", "__pycache__", "node_modules", ".venv", "venv", "dist", "build",
|
|
340
374
|
".mypy_cache", ".pytest_cache", ".ruff_cache",
|
|
@@ -344,33 +378,45 @@ DEFAULT_EXCLUDED_DIRS = {
|
|
|
344
378
|
def get_exclude_patterns() -> set:
|
|
345
379
|
"""
|
|
346
380
|
Returns the set of directory/file exclude patterns.
|
|
347
|
-
Combines default excluded dirs with exclude_patterns from:
|
|
381
|
+
Combines default excluded dirs with exclude_patterns and exclude from:
|
|
348
382
|
1. config.json (machine-specific)
|
|
349
|
-
2. doc_config.json (coverage_settings.exclude_patterns
|
|
383
|
+
2. doc_config.json (coverage_settings.exclude_patterns, coverage_settings.exclude,
|
|
384
|
+
top-level exclude_patterns, top-level exclude)
|
|
350
385
|
3. ki_config.json
|
|
386
|
+
4. .gitignore in project root
|
|
351
387
|
"""
|
|
352
388
|
patterns = set(DEFAULT_EXCLUDED_DIRS)
|
|
353
389
|
|
|
390
|
+
def _add_from_cfg(cfg_dict, key):
|
|
391
|
+
val = cfg_dict.get(key, [])
|
|
392
|
+
if isinstance(val, list):
|
|
393
|
+
for p in val:
|
|
394
|
+
if p:
|
|
395
|
+
patterns.add(str(p))
|
|
396
|
+
elif isinstance(val, str) and val:
|
|
397
|
+
patterns.add(val)
|
|
398
|
+
|
|
354
399
|
# 1. config.json
|
|
355
400
|
local_cfg = get_local_config()
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
patterns.add(str(p))
|
|
401
|
+
_add_from_cfg(local_cfg, "exclude_patterns")
|
|
402
|
+
_add_from_cfg(local_cfg, "exclude")
|
|
359
403
|
|
|
360
404
|
# 2. doc_config.json
|
|
361
405
|
doc_cfg = get_doc_config()
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
406
|
+
_add_from_cfg(doc_cfg, "exclude_patterns")
|
|
407
|
+
_add_from_cfg(doc_cfg, "exclude")
|
|
408
|
+
cov_cfg = doc_cfg.get("coverage_settings", {})
|
|
409
|
+
if isinstance(cov_cfg, dict):
|
|
410
|
+
_add_from_cfg(cov_cfg, "exclude_patterns")
|
|
411
|
+
_add_from_cfg(cov_cfg, "exclude")
|
|
368
412
|
|
|
369
413
|
# 3. ki_config.json
|
|
370
414
|
ki_cfg = get_ki_cfg()
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
415
|
+
_add_from_cfg(ki_cfg, "exclude_patterns")
|
|
416
|
+
_add_from_cfg(ki_cfg, "exclude")
|
|
417
|
+
|
|
418
|
+
# 4. .gitignore
|
|
419
|
+
patterns.update(load_gitignore_patterns())
|
|
374
420
|
|
|
375
421
|
return patterns
|
|
376
422
|
|
|
@@ -389,15 +435,17 @@ def should_exclude(name: str, rel_path: str = "", patterns: set = None) -> bool:
|
|
|
389
435
|
norm_rel = rel_path.replace("\\", "/").strip("/") if rel_path else ""
|
|
390
436
|
|
|
391
437
|
for pat in patterns:
|
|
392
|
-
|
|
438
|
+
norm_pat = pat.replace("\\", "/").strip("/")
|
|
439
|
+
if not norm_pat:
|
|
440
|
+
continue
|
|
441
|
+
if fnmatch.fnmatch(name, norm_pat):
|
|
393
442
|
return True
|
|
394
443
|
if norm_rel:
|
|
395
|
-
norm_pat = pat.replace("\\", "/").strip("/")
|
|
396
444
|
if norm_rel == norm_pat or norm_rel.startswith(norm_pat + "/"):
|
|
397
445
|
return True
|
|
398
446
|
if fnmatch.fnmatch(norm_rel, norm_pat):
|
|
399
447
|
return True
|
|
400
|
-
if any(fnmatch.fnmatch(comp,
|
|
448
|
+
if any(fnmatch.fnmatch(comp, norm_pat) for comp in norm_rel.split("/")):
|
|
401
449
|
return True
|
|
402
450
|
|
|
403
451
|
return False
|
|
@@ -458,6 +506,122 @@ def get_ki_list_table() -> str:
|
|
|
458
506
|
return "\n".join(rows)
|
|
459
507
|
|
|
460
508
|
|
|
509
|
+
def get_ki_list_data(scope: str = "all", filter_query: Optional[str] = None) -> Dict[str, Any]:
|
|
510
|
+
"""Collects structured list of Knowledge Items and ADRs with optional filtering."""
|
|
511
|
+
jail = get_knowledge_root()
|
|
512
|
+
proj = get_project_root()
|
|
513
|
+
dec_dirs = get_decisions_dirs(proj, jail)
|
|
514
|
+
doc_config = get_doc_config() or {}
|
|
515
|
+
items = doc_config.get("knowledge_items", {})
|
|
516
|
+
|
|
517
|
+
flt = filter_query.lower().strip() if filter_query else None
|
|
518
|
+
|
|
519
|
+
ki_list = []
|
|
520
|
+
if scope in ("all", "ki"):
|
|
521
|
+
for name, info in sorted(items.items()):
|
|
522
|
+
summary = info.get("summary", info.get("description", "—"))
|
|
523
|
+
covers = info.get("covers", [])
|
|
524
|
+
display_name = name
|
|
525
|
+
if "path" in info and info["path"]:
|
|
526
|
+
display_name = info["path"]
|
|
527
|
+
|
|
528
|
+
if flt:
|
|
529
|
+
covers_str = " ".join(covers).lower() if isinstance(covers, list) else str(covers).lower()
|
|
530
|
+
if flt not in name.lower() and flt not in summary.lower() and flt not in covers_str and flt not in display_name.lower():
|
|
531
|
+
continue
|
|
532
|
+
|
|
533
|
+
ki_list.append({
|
|
534
|
+
"name": name,
|
|
535
|
+
"path": display_name,
|
|
536
|
+
"summary": summary,
|
|
537
|
+
"covers": covers,
|
|
538
|
+
"type": "ki",
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
adr_list = []
|
|
542
|
+
if scope in ("all", "adr"):
|
|
543
|
+
adr_records = []
|
|
544
|
+
seen_files = set()
|
|
545
|
+
for c in dec_dirs:
|
|
546
|
+
if os.path.exists(c) and os.path.isdir(c):
|
|
547
|
+
for f in sorted(os.listdir(c)):
|
|
548
|
+
if f.endswith(".md") and f not in seen_files:
|
|
549
|
+
seen_files.add(f)
|
|
550
|
+
full_p = os.path.join(c, f)
|
|
551
|
+
adr_records.append(parse_adr_file(full_p, proj))
|
|
552
|
+
|
|
553
|
+
def _sort_key(r):
|
|
554
|
+
num = int(r["id"]) if r["id"].isdigit() else 999999
|
|
555
|
+
return (num, r["file"])
|
|
556
|
+
adr_records.sort(key=_sort_key)
|
|
557
|
+
|
|
558
|
+
for r in adr_records:
|
|
559
|
+
if flt:
|
|
560
|
+
target_text = f"{r['id']} {r['title']} {r['summary']} {r['file']} {r['status']}".lower()
|
|
561
|
+
if flt not in target_text:
|
|
562
|
+
continue
|
|
563
|
+
adr_list.append({
|
|
564
|
+
"id": r["id"],
|
|
565
|
+
"title": r["title"],
|
|
566
|
+
"status": r["status"],
|
|
567
|
+
"date": r["date"],
|
|
568
|
+
"summary": r["summary"],
|
|
569
|
+
"file": r["file"],
|
|
570
|
+
"rel_path": r["rel_path"],
|
|
571
|
+
"type": "adr",
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
"scope": scope,
|
|
576
|
+
"filter": filter_query,
|
|
577
|
+
"total_ki": len(ki_list),
|
|
578
|
+
"total_adr": len(adr_list),
|
|
579
|
+
"knowledge_items": ki_list,
|
|
580
|
+
"adrs": adr_list,
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def format_ki_list_markdown(data: Dict[str, Any]) -> str:
|
|
585
|
+
"""Formats KI and ADR list into clean Markdown tables."""
|
|
586
|
+
scope = data.get("scope", "all")
|
|
587
|
+
flt = data.get("filter")
|
|
588
|
+
ki_list = data.get("knowledge_items", [])
|
|
589
|
+
adr_list = data.get("adrs", [])
|
|
590
|
+
|
|
591
|
+
sections = []
|
|
592
|
+
flt_msg = f" matching '{flt}'" if flt else ""
|
|
593
|
+
|
|
594
|
+
if scope in ("all", "ki"):
|
|
595
|
+
if ki_list:
|
|
596
|
+
rows = [
|
|
597
|
+
f"### Knowledge Items ({len(ki_list)}){flt_msg}\n",
|
|
598
|
+
"| File | Summary / Topic | Covered Modules |",
|
|
599
|
+
"|------|-----------------|-----------------|"
|
|
600
|
+
]
|
|
601
|
+
for item in ki_list:
|
|
602
|
+
covers_str = ", ".join(f"`{c}`" for c in item.get("covers", [])) if item.get("covers") else "—"
|
|
603
|
+
rows.append(f"| `{item['path']}` | {item['summary']} | {covers_str} |")
|
|
604
|
+
sections.append("\n".join(rows))
|
|
605
|
+
else:
|
|
606
|
+
sections.append(f"No Knowledge Items{flt_msg} found.")
|
|
607
|
+
|
|
608
|
+
if scope in ("all", "adr"):
|
|
609
|
+
if adr_list:
|
|
610
|
+
rows = [
|
|
611
|
+
f"### Architecture Decision Records ({len(adr_list)}){flt_msg}\n",
|
|
612
|
+
"| ID | Title | Status | Date | File |",
|
|
613
|
+
"|----|-------|--------|------|------|"
|
|
614
|
+
]
|
|
615
|
+
for r in adr_list:
|
|
616
|
+
link = f"[{r['file']}]({r['rel_path']})"
|
|
617
|
+
rows.append(f"| {r['id']} | {r['title']} | {r['status']} | {r['date']} | {link} |")
|
|
618
|
+
sections.append("\n".join(rows))
|
|
619
|
+
elif scope == "adr":
|
|
620
|
+
sections.append(f"No ADRs{flt_msg} found.")
|
|
621
|
+
|
|
622
|
+
return "\n\n".join(sections)
|
|
623
|
+
|
|
624
|
+
|
|
461
625
|
def save_doc_config(config: dict) -> str:
|
|
462
626
|
"""Atomically saves doc_config.json for the active project."""
|
|
463
627
|
path = get_doc_config_path()
|
|
@@ -80,12 +80,33 @@ CURRENT_TOOL_MODE: str = os.environ.get("KI_TOOL_MODE", "full").lower()
|
|
|
80
80
|
GLOBAL_INSTRUCTIONS = """\
|
|
81
81
|
# Global AI Instructions for ki-manager
|
|
82
82
|
|
|
83
|
-
## 1. Project Navigation
|
|
83
|
+
## 1. Project Navigation & Architecture
|
|
84
|
+
`{{KI_DIR}}` (knowledge_root) is the **system container** for the entire knowledge infrastructure (analogous to `.git/` in Git).
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
<project_root>/
|
|
88
|
+
├── {{KI_DIR}}/ ← SYSTEM CONTAINER (do not pollute with .ki.md)
|
|
89
|
+
│ ├── config.json ← Machine-specific environment config (venv path, etc.)
|
|
90
|
+
│ ├── ki_config.json ← Project knowledge configuration
|
|
91
|
+
│ ├── doc_config.json ← Manifest of tracked modules and Knowledge Items
|
|
92
|
+
│ ├── DIR_INDEX.md ← Auto-generated project directory tree
|
|
93
|
+
│ ├── coverage_matrix.md ← Audit report (from audit_coverage)
|
|
94
|
+
│ ├── knowledge/ ← STRICTLY ALL Knowledge Items (*.ki.md) live HERE!
|
|
95
|
+
│ │ ├── _OVERVIEW.ki.md ← Project overview stub (MUST NOT be deleted)
|
|
96
|
+
│ │ └── <module>.ki.md ← Module Knowledge Items
|
|
97
|
+
│ └── decisions/ ← Architectural Decision Records (*.md ADRs)
|
|
98
|
+
├── AGENTS.md ← Project instructions for AI agents in harness
|
|
99
|
+
└── CLAUDE.md ← Instructions for Claude Code / Anthropic
|
|
100
|
+
```
|
|
101
|
+
|
|
84
102
|
- **`DIR_INDEX.md`** (`{{KI_DIR}}/DIR_INDEX.md`) — project directory tree.
|
|
85
103
|
- **`doc_config.json`** (`{{KI_DIR}}/doc_config.json`) — manifest of tracked artifacts.
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
|
|
104
|
+
- **`_OVERVIEW.ki.md`** (`{{KI_DIR}}/knowledge/_OVERVIEW.ki.md`) — start here for high-level architecture.
|
|
105
|
+
- **Harness & Agent Instructions Guide**: see `ki://agent-rules.md` or `ki_instructions(document="agent-rules")` for recommended snippets for `AGENTS.md` and `CLAUDE.md`.
|
|
106
|
+
|
|
107
|
+
> [!CAUTION]
|
|
108
|
+
> 1. NEVER place `*.ki.md` files directly in the root of `{{KI_DIR}}/`. All Knowledge Items must reside in `{{KI_DIR}}/knowledge/`.
|
|
109
|
+
> 2. NEVER delete `{{KI_DIR}}/knowledge/`. The initial `_OVERVIEW.ki.md` (~640 bytes) is a bootstrap stub, not a duplicate. Deleting `knowledge/` breaks audit, scaffolding, and dependency analysis.
|
|
89
110
|
|
|
90
111
|
## 2. Forced Efficiency (Anti-Hallucinations)
|
|
91
112
|
1. **Mandatory Planning Template**:
|
|
@@ -97,12 +118,12 @@ GLOBAL_INSTRUCTIONS = """\
|
|
|
97
118
|
2. **Strict Adherence**:
|
|
98
119
|
- Always read relevant KIs before modifying a module.
|
|
99
120
|
<!-- if-compact -->
|
|
100
|
-
- Use `ki_search(query="...")` and `ki_read(rel_path="...")` to
|
|
121
|
+
- Use `ki_list()` to view all documented items, `ki_search(query="...")` to find topics, and `ki_read(rel_path="...")` to read documentation.
|
|
101
122
|
- For read-only analysis tools (`audit_coverage`, `generate_dir_index`, `ki_scaffold_status`, `git_diff_secured`), call `ki_call(tool="...")`.
|
|
102
123
|
- For state-modifying operations (`git_checkpoint`, `write_know_file`, `edit_know_file`, `ki_scaffold`, `create_adr`), call `ki_mutate(tool="...", args={...})`.
|
|
103
124
|
- To inspect schemas of available tools, call `ki_tools(name="<tool_name>")`.
|
|
104
125
|
<!-- else-compact -->
|
|
105
|
-
- Use `ki_search(query="...")` and `ki_read(rel_path="...")` or `read_know_file(rel_path="...")` to explore documentation.
|
|
126
|
+
- Use `ki_list()` to view all documented items, `ki_search(query="...")` to find topics, and `ki_read(rel_path="...")` or `read_know_file(rel_path="...")` to explore documentation.
|
|
106
127
|
- After significant changes, run `audit_coverage` via MCP.
|
|
107
128
|
- Use `git_checkpoint` to save knowledge snapshots.
|
|
108
129
|
<!-- /if-compact -->
|
|
@@ -1038,7 +1059,8 @@ MCP_TOOLS = [
|
|
|
1038
1059
|
"idempotentHint": True,
|
|
1039
1060
|
},
|
|
1040
1061
|
},
|
|
1041
|
-
# ─── Search & Read ───
|
|
1062
|
+
# ─── Navigation, Search & Read ───
|
|
1063
|
+
facade.KI_LIST_TOOL,
|
|
1042
1064
|
facade.KI_SEARCH_TOOL,
|
|
1043
1065
|
facade.KI_READ_TOOL,
|
|
1044
1066
|
]
|
|
@@ -1049,6 +1071,7 @@ facade.sync_dispatch_schemas(MCP_TOOLS)
|
|
|
1049
1071
|
_INSTRUCTIONS_TOOL = next(t for t in MCP_TOOLS if t["name"] == "ki_instructions")
|
|
1050
1072
|
COMPACT_TOOLS = [
|
|
1051
1073
|
_INSTRUCTIONS_TOOL,
|
|
1074
|
+
facade.KI_LIST_TOOL,
|
|
1052
1075
|
facade.KI_SEARCH_TOOL,
|
|
1053
1076
|
facade.KI_READ_TOOL,
|
|
1054
1077
|
facade.KI_TOOLS_TOOL,
|
|
@@ -1970,14 +1993,29 @@ def handle_tool_call(name: str, args: dict) -> Any:
|
|
|
1970
1993
|
return ke.restore_mapping()
|
|
1971
1994
|
return str(ke.save_state(ke.capture_full_state()))
|
|
1972
1995
|
|
|
1973
|
-
# ─── Search & Read ───
|
|
1996
|
+
# ─── Navigation, Search & Read ───
|
|
1997
|
+
if name == "ki_list":
|
|
1998
|
+
scope = args.get("scope", "all")
|
|
1999
|
+
fmt = args.get("format", "markdown")
|
|
2000
|
+
flt = args.get("filter")
|
|
2001
|
+
data = ki_utils.get_ki_list_data(scope=scope, filter_query=flt)
|
|
2002
|
+
if fmt == "json":
|
|
2003
|
+
return json.dumps(data, ensure_ascii=False, indent=2)
|
|
2004
|
+
return ki_utils.format_ki_list_markdown(data)
|
|
2005
|
+
|
|
1974
2006
|
if name == "ki_search":
|
|
1975
2007
|
query = args.get("query", "").strip()
|
|
1976
2008
|
scope = args.get("scope", "all")
|
|
1977
2009
|
limit = int(args.get("limit", 5))
|
|
1978
2010
|
offset = int(args.get("offset", 0))
|
|
2011
|
+
page = args.get("page")
|
|
2012
|
+
if page is not None:
|
|
2013
|
+
try:
|
|
2014
|
+
page = int(page)
|
|
2015
|
+
except (ValueError, TypeError):
|
|
2016
|
+
page = None
|
|
1979
2017
|
fmt = args.get("format", "markdown")
|
|
1980
|
-
res = ki_search.search_knowledge(query, project_root=get_project_root(), scope=scope, limit=limit, offset=offset)
|
|
2018
|
+
res = ki_search.search_knowledge(query, project_root=get_project_root(), scope=scope, limit=limit, offset=offset, page=page)
|
|
1981
2019
|
if fmt == "json":
|
|
1982
2020
|
return json.dumps(res, ensure_ascii=False, indent=2)
|
|
1983
2021
|
return ki_search.format_search_markdown(res)
|
|
@@ -2273,6 +2311,7 @@ def main():
|
|
|
2273
2311
|
jail = get_jail_dir()
|
|
2274
2312
|
resources = [
|
|
2275
2313
|
{"uri": "ki://instructions.md", "name": "instructions.md (Global AI Rules)", "mimeType": "text/markdown"},
|
|
2314
|
+
{"uri": "ki://agent-rules.md", "name": "agent-rules.md (Harness & Agent Instructions Guide)", "mimeType": "text/markdown"},
|
|
2276
2315
|
{"uri": "ki://knowledge-items.md", "name": "knowledge-items.md (Dynamic KI List)", "mimeType": "text/markdown"},
|
|
2277
2316
|
{"uri": "ki://adr-list.md", "name": "adr-list.md (Dynamic ADR List)", "mimeType": "text/markdown"},
|
|
2278
2317
|
]
|
|
@@ -2319,6 +2358,15 @@ def main():
|
|
|
2319
2358
|
if uri == "ki://instructions.md":
|
|
2320
2359
|
know_name = os.path.basename(ki_utils.get_knowledge_root()) or ".ki-base"
|
|
2321
2360
|
content = facade.render_instruction(GLOBAL_INSTRUCTIONS, CURRENT_TOOL_MODE, know_name)
|
|
2361
|
+
elif uri == "ki://agent-rules.md":
|
|
2362
|
+
wf_path = (_WORKFLOWS_DIR / "agent-rules.md").resolve()
|
|
2363
|
+
if wf_path.exists():
|
|
2364
|
+
with open(wf_path, "r", encoding="utf-8") as f:
|
|
2365
|
+
raw = f.read()
|
|
2366
|
+
know_name = os.path.basename(ki_utils.get_knowledge_root()) or ".ki-base"
|
|
2367
|
+
content = facade.render_instruction(raw, CURRENT_TOOL_MODE, know_name)
|
|
2368
|
+
else:
|
|
2369
|
+
content = "agent-rules.md not found."
|
|
2322
2370
|
elif uri == "ki://knowledge-items.md":
|
|
2323
2371
|
ki_table = ki_utils.get_ki_list_table()
|
|
2324
2372
|
adr_table = ki_utils.get_adr_table()
|
|
@@ -49,6 +49,7 @@ TOOL_GROUPS: Dict[str, List[str]] = {
|
|
|
49
49
|
"create_adr",
|
|
50
50
|
],
|
|
51
51
|
"files": [
|
|
52
|
+
"ki_list",
|
|
52
53
|
"read_know_file",
|
|
53
54
|
"write_know_file",
|
|
54
55
|
"edit_know_file",
|
|
@@ -66,6 +67,41 @@ TOOL_GROUPS: Dict[str, List[str]] = {
|
|
|
66
67
|
|
|
67
68
|
# ─── Tool Schemas ─────────────────────────────────────────────────────────────
|
|
68
69
|
|
|
70
|
+
KI_LIST_TOOL = {
|
|
71
|
+
"name": "ki_list",
|
|
72
|
+
"description": (
|
|
73
|
+
"List Knowledge Items (KI) and Architecture Decision Records (ADRs) registered in the active project. "
|
|
74
|
+
"Provides a structured overview of all documented modules, topics, and architectural decisions."
|
|
75
|
+
),
|
|
76
|
+
"inputSchema": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": {
|
|
79
|
+
"scope": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"enum": ["all", "ki", "adr"],
|
|
82
|
+
"default": "all",
|
|
83
|
+
"description": "Filter by document type: 'all' (default), 'ki' (Knowledge Items), or 'adr' (Architecture Decision Records).",
|
|
84
|
+
},
|
|
85
|
+
"format": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"enum": ["markdown", "json"],
|
|
88
|
+
"default": "markdown",
|
|
89
|
+
"description": "Output format: 'markdown' (structured tables, default) or 'json'.",
|
|
90
|
+
},
|
|
91
|
+
"filter": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "Optional substring to filter by filename, title, summary, or covered module.",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
"annotations": {
|
|
98
|
+
"title": "List Knowledge Items & ADRs",
|
|
99
|
+
"readOnlyHint": True,
|
|
100
|
+
"destructiveHint": False,
|
|
101
|
+
"idempotentHint": True,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
69
105
|
KI_SEARCH_TOOL = {
|
|
70
106
|
"name": "ki_search",
|
|
71
107
|
"description": (
|
|
@@ -96,6 +132,12 @@ KI_SEARCH_TOOL = {
|
|
|
96
132
|
"default": 0,
|
|
97
133
|
"description": "Result offset for pagination (default: 0).",
|
|
98
134
|
},
|
|
135
|
+
"page": {
|
|
136
|
+
"type": "integer",
|
|
137
|
+
"minimum": 1,
|
|
138
|
+
"default": 1,
|
|
139
|
+
"description": "Page number for pagination (1-based, alternative to offset).",
|
|
140
|
+
},
|
|
99
141
|
"format": {
|
|
100
142
|
"type": "string",
|
|
101
143
|
"enum": ["markdown", "json"],
|
|
@@ -245,7 +287,7 @@ KI_MUTATE_TOOL = {
|
|
|
245
287
|
|
|
246
288
|
|
|
247
289
|
# Direct facade tools that cannot be called recursively via ki_call / ki_mutate
|
|
248
|
-
DIRECT_FACADE_TOOLS = {"ki_instructions", "ki_search", "ki_read", "ki_tools", "ki_call", "ki_mutate"}
|
|
290
|
+
DIRECT_FACADE_TOOLS = {"ki_instructions", "ki_list", "ki_search", "ki_read", "ki_tools", "ki_call", "ki_mutate"}
|
|
249
291
|
|
|
250
292
|
|
|
251
293
|
def sync_dispatch_schemas(all_tools: List[Dict[str, Any]]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ki-manager-agent-rules
|
|
3
|
+
description: Recommendations and reference snippets for configuring project-level AI instruction files (AGENTS.md, CLAUDE.md, .cursorrules) with ki-manager
|
|
4
|
+
metadata:
|
|
5
|
+
author: ki-manager
|
|
6
|
+
version: "1.0"
|
|
7
|
+
<!-- if-compact -->
|
|
8
|
+
allowed-tools: ki_instructions ki_read ki_search ki_tools ki_call ki_mutate
|
|
9
|
+
<!-- else-compact -->
|
|
10
|
+
allowed-tools: ki_instructions ki_read sync_agents_md
|
|
11
|
+
<!-- /if-compact -->
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# /agent-rules — AI Rules & Guidelines for Project Harnesses (AGENTS.md, CLAUDE.md, IDE Rules)
|
|
15
|
+
|
|
16
|
+
> [!IMPORTANT]
|
|
17
|
+
> **Purpose of this Document**:
|
|
18
|
+
> This document is **NOT a system file or magical hook** that automatically or invisibly directs AI agent behavior by its mere presence.
|
|
19
|
+
> Rather, it is a **reference guide and collection of actionable snippets and recommendations** for developers and agents to configure project-level instruction files defined in the project harness (`AGENTS.md`, `CLAUDE.md`, `.cursorrules`, `.windsurfrules`).
|
|
20
|
+
> Its goal is to ensure external AI agents understand the `ki-manager` architecture and use the available tools properly.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Knowledge Base Directory Layout
|
|
25
|
+
|
|
26
|
+
Every AI agent working with the repository must distinguish between the **system container** and the **knowledge items directory**:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
<project_root>/
|
|
30
|
+
├── {{KI_DIR}}/ ← SYSTEM CONTAINER (like .git/ for Git)
|
|
31
|
+
│ ├── config.json ← Environment config (Python venv path, etc.)
|
|
32
|
+
│ ├── ki_config.json ← Knowledge base configuration (knowledge_root: "{{KI_DIR}}")
|
|
33
|
+
│ ├── doc_config.json ← Manifest and registry of tracked modules & KIs
|
|
34
|
+
│ ├── DIR_INDEX.md ← Auto-generated project directory tree
|
|
35
|
+
│ ├── coverage_matrix.md ← Coverage audit report (generated by audit_coverage)
|
|
36
|
+
│ ├── knowledge/ ← STRICTLY ALL Knowledge Items (*.ki.md) live HERE!
|
|
37
|
+
│ │ ├── _OVERVIEW.ki.md ← High-level project architecture stub (DO NOT DELETE!)
|
|
38
|
+
│ │ └── <module>.ki.md ← Knowledge Items for individual modules
|
|
39
|
+
│ └── decisions/ ← Architecture Decision Records (*.md ADRs)
|
|
40
|
+
├── AGENTS.md ← Instructions for Antigravity & general AI agents in harness
|
|
41
|
+
└── CLAUDE.md ← Instructions for Claude Code / Anthropic
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> [!CAUTION]
|
|
45
|
+
> **Common Agent Pitfalls:**
|
|
46
|
+
> 1. **`knowledge_root: "{{KI_DIR}}"` points to the root of the system container, NOT the folder for `.ki.md` files.**
|
|
47
|
+
> NEVER write `*.ki.md` files directly into the root of `{{KI_DIR}}/`.
|
|
48
|
+
> 2. **NEVER delete `{{KI_DIR}}/knowledge/`!**
|
|
49
|
+
> The initial `_OVERVIEW.ki.md` (~640 bytes) is NOT a stale duplicate. It is the initial bootstrap stub created for enrichment. Deleting `knowledge/` breaks all analysis and scaffolding tools (`audit_coverage`, `analyze_dependencies`, `ki_scaffold`, `finalize_ki_scaffolds`).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. Reference Snippet for `AGENTS.md` (Antigravity & General AI Agents)
|
|
54
|
+
|
|
55
|
+
Add the following block to your project's `AGENTS.md` (or use `sync_agents_md` to keep tables synchronized):
|
|
56
|
+
|
|
57
|
+
```markdown
|
|
58
|
+
# AI Agent Guidelines (AGENTS.md)
|
|
59
|
+
|
|
60
|
+
This project uses **ki-manager** (an MCP server for architectural knowledge management).
|
|
61
|
+
|
|
62
|
+
## Knowledge Base Architecture
|
|
63
|
+
- System container: `{{KI_DIR}}/`
|
|
64
|
+
- Knowledge Items (KIs): strictly inside `{{KI_DIR}}/knowledge/*.ki.md`
|
|
65
|
+
- Architecture Decision Records (ADRs): in `{{KI_DIR}}/decisions/*.md` (or `decisions/`)
|
|
66
|
+
- Project overview & entry point: `{{KI_DIR}}/knowledge/_OVERVIEW.ki.md`
|
|
67
|
+
- Project directory tree: `{{KI_DIR}}/DIR_INDEX.md`
|
|
68
|
+
|
|
69
|
+
## Agent Operating Rules
|
|
70
|
+
1. **Context Before Changes**:
|
|
71
|
+
- Inspect `{{KI_DIR}}/knowledge/_OVERVIEW.ki.md` first.
|
|
72
|
+
- Read relevant module KIs via `ki_read(rel_path="...")` or `read_know_file` before writing code.
|
|
73
|
+
- List read KIs and architectural constraints in your Implementation Plan.
|
|
74
|
+
2. **File Safety**:
|
|
75
|
+
- NEVER delete `{{KI_DIR}}/knowledge/` and never save `.ki.md` files directly in `{{KI_DIR}}/` root.
|
|
76
|
+
- Use `ki-manager` tools (`write_know_file`, `edit_know_file`) rather than generic filesystem tools when modifying documentation.
|
|
77
|
+
3. **Synchronization**:
|
|
78
|
+
- When modifying module logic, update the corresponding KI and run `sync_agents_md`.
|
|
79
|
+
|
|
80
|
+
## Knowledge Items
|
|
81
|
+
<!-- Auto-synchronized by sync_agents_md -->
|
|
82
|
+
| File | Topic / Summary |
|
|
83
|
+
|------|-----------------|
|
|
84
|
+
| `_OVERVIEW.ki.md` | Project overview |
|
|
85
|
+
|
|
86
|
+
## Architecture Decision Records (ADRs)
|
|
87
|
+
<!-- Auto-synchronized by sync_agents_md -->
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 3. Reference Snippet for `CLAUDE.md` (Claude Code / Anthropic)
|
|
93
|
+
|
|
94
|
+
Add the following block to your project's `CLAUDE.md`:
|
|
95
|
+
|
|
96
|
+
```markdown
|
|
97
|
+
# Project Guidelines for Claude Code
|
|
98
|
+
|
|
99
|
+
This project uses `ki-manager` MCP server for knowledge management.
|
|
100
|
+
|
|
101
|
+
## Knowledge Base Architecture
|
|
102
|
+
- System container: `{{KI_DIR}}/`
|
|
103
|
+
- Knowledge Items (KIs): strictly inside `{{KI_DIR}}/knowledge/*.ki.md`
|
|
104
|
+
- Architecture Decision Records (ADRs): inside `{{KI_DIR}}/decisions/*.md`
|
|
105
|
+
- Project overview: `{{KI_DIR}}/knowledge/_OVERVIEW.ki.md`
|
|
106
|
+
|
|
107
|
+
## Core Protocols
|
|
108
|
+
- **Always read context first**: Check `{{KI_DIR}}/knowledge/_OVERVIEW.ki.md` and relevant KIs before implementing changes.
|
|
109
|
+
- **Never mutate container root**: Do NOT write `*.ki.md` directly into `{{KI_DIR}}/`. Never delete `{{KI_DIR}}/knowledge/`.
|
|
110
|
+
- **Tooling**: Prefer MCP tools (`ki_read`, `ki_search`, `write_know_file`) when inspecting or updating documentation.
|
|
111
|
+
- **Verification**: Run tests via project virtual environment (`.venv`).
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 4. Reference Snippet for Cursor (`.cursorrules` or `.cursor/rules/ki-manager.mdc`)
|
|
117
|
+
|
|
118
|
+
```markdown
|
|
119
|
+
---
|
|
120
|
+
description: Rules for navigating and updating ki-manager knowledge base
|
|
121
|
+
globs: ["**/*"]
|
|
122
|
+
alwaysApply: true
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
# ki-manager Integration Rules
|
|
126
|
+
|
|
127
|
+
- Project architecture is documented via Knowledge Items (KIs) located in `{{KI_DIR}}/knowledge/`.
|
|
128
|
+
- Start exploring codebase with `{{KI_DIR}}/knowledge/_OVERVIEW.ki.md` and `{{KI_DIR}}/DIR_INDEX.md`.
|
|
129
|
+
- When writing or updating documentation, always keep `*.ki.md` files inside `{{KI_DIR}}/knowledge/`.
|
|
130
|
+
- Do NOT delete or modify the container directory structure (`{{KI_DIR}}/`).
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 5. Tooling Interaction
|
|
136
|
+
|
|
137
|
+
Depending on the active `--tool-mode` of the `ki-manager` MCP server, use the corresponding tools:
|
|
138
|
+
|
|
139
|
+
<!-- if-compact -->
|
|
140
|
+
- **Search documentation**: `ki_search(query="<keywords>")`
|
|
141
|
+
- **Read KI or ADR**: `ki_read(rel_path="<filename>.ki.md")`
|
|
142
|
+
- **Inspect tool catalog**: `ki_tools()`
|
|
143
|
+
- **Coverage audit**: `ki_call(tool="audit_coverage")`
|
|
144
|
+
- **Write / update KI**: `ki_mutate(tool="write_know_file", args={"rel_path": "<name>.ki.md", "content": "..."})`
|
|
145
|
+
- **Sync AGENTS.md**: `ki_mutate(tool="sync_agents_md")`
|
|
146
|
+
<!-- else-compact -->
|
|
147
|
+
- **Search documentation**: `ki_search(query="<keywords>")`
|
|
148
|
+
- **Read KI or ADR**: `ki_read(rel_path="<filename>.ki.md")` or `read_know_file(rel_path="<filename>.ki.md")`
|
|
149
|
+
- **Coverage audit**: `audit_coverage()`
|
|
150
|
+
- **Write / update KI**: `write_know_file(rel_path="<name>.ki.md", content="...")`
|
|
151
|
+
- **Sync AGENTS.md**: `sync_agents_md()`
|
|
152
|
+
<!-- /if-compact -->
|
{ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/workflows/expand-knowledge.md
RENAMED
|
@@ -54,7 +54,9 @@ Select **one** target with the highest priority in the following order:
|
|
|
54
54
|
|
|
55
55
|
## Safety & Tooling Rules (Mandatory)
|
|
56
56
|
> [!IMPORTANT]
|
|
57
|
-
>
|
|
57
|
+
> - `{{KI_DIR}}` (knowledge_root) is the system container. All Knowledge Items (`*.ki.md`) live strictly in `{{KI_DIR}}/knowledge/`.
|
|
58
|
+
> - **NEVER** write `.ki.md` files directly into `{{KI_DIR}}/` root, and **NEVER** delete `{{KI_DIR}}/knowledge/`.
|
|
59
|
+
> - When working with this workflow, it is **STRICTLY PROHIBITED** to use general file editing tools (e.g., `filesystem.edit_file`) for files inside the `{{KI_DIR}}` directory.
|
|
58
60
|
>
|
|
59
61
|
<!-- if-compact -->
|
|
60
62
|
> You **MUST** use the following MCP tools from the `ki-manager` server:
|
|
@@ -94,7 +96,7 @@ Select **one** target with the highest priority in the following order:
|
|
|
94
96
|
|
|
95
97
|
## Step 4 — Create or Update a KI File
|
|
96
98
|
|
|
97
|
-
Write the file using `write_know_file` or `edit_know_file
|
|
99
|
+
Write the file using `write_know_file` or `edit_know_file` (passing `rel_path="<filename>.ki.md"` automatically writes inside `{{KI_DIR}}/knowledge/`):
|
|
98
100
|
<!-- if-compact -->
|
|
99
101
|
// turbo
|
|
100
102
|
`ki_mutate(tool="write_know_file", args={"rel_path": "<filename>.ki.md", "content": "..."})`
|
{ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/workflows/scaffold-knowledge.md
RENAMED
|
@@ -135,6 +135,10 @@ Replace the stub with the enriched version:
|
|
|
135
135
|
`write_know_file(rel_path="<filename>.ki.md", content="...")`
|
|
136
136
|
<!-- /if-compact -->
|
|
137
137
|
|
|
138
|
+
> [!NOTE]
|
|
139
|
+
> All Knowledge Items live in `{{KI_DIR}}/knowledge/`. Calling `write_know_file(rel_path="<filename>.ki.md", ...)` automatically targets `{{KI_DIR}}/knowledge/<filename>.ki.md`.
|
|
140
|
+
> **Never** write `.ki.md` files to the root of `{{KI_DIR}}/`, and **never** delete `{{KI_DIR}}/knowledge/` (including `_OVERVIEW.ki.md`, which is the project bootstrap stub).
|
|
141
|
+
|
|
138
142
|
> [!IMPORTANT]
|
|
139
143
|
> The **first line must be `<!-- scaffold: enriched -->`**. This marker tells `ki_finalize_scaffolds` to process the file in Phase 3 (clean markers, update doc_config summary). The `Related KIs` section is left empty — it will be populated automatically in Phase 3.
|
|
140
144
|
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
import argparse
|
|
4
|
-
import json
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import List
|
|
7
|
-
|
|
8
|
-
# Add path to ki_utils
|
|
9
|
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
10
|
-
import ki_utils
|
|
11
|
-
|
|
12
|
-
def get_unmapped_files(target_path: str = ".") -> List[str]:
|
|
13
|
-
"""Returns a sorted list of unmapped files relative to project root."""
|
|
14
|
-
project_root = ki_utils.get_project_root()
|
|
15
|
-
doc_config = ki_utils.get_doc_config()
|
|
16
|
-
|
|
17
|
-
if not project_root or not doc_config:
|
|
18
|
-
return []
|
|
19
|
-
|
|
20
|
-
# 1. Collect all files that are already mapped in doc_config.json
|
|
21
|
-
mapped_paths = set()
|
|
22
|
-
knowledge_items = doc_config.get("knowledge_items", {})
|
|
23
|
-
|
|
24
|
-
for ki in knowledge_items.values():
|
|
25
|
-
depends_on = ki.get("depends_on", [])
|
|
26
|
-
for p in depends_on:
|
|
27
|
-
abs_p = os.path.abspath(os.path.join(project_root, p))
|
|
28
|
-
mapped_paths.add(abs_p)
|
|
29
|
-
|
|
30
|
-
# 2. Scan the target directory
|
|
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 []
|
|
34
|
-
if not os.path.exists(abs_target):
|
|
35
|
-
return []
|
|
36
|
-
|
|
37
|
-
unmapped = []
|
|
38
|
-
ignored_exts = {'.pyc', '.pyo', '.pyd', '.obj', '.dll', '.exe', '.bin'}
|
|
39
|
-
exclude_patterns = ki_utils.get_exclude_patterns()
|
|
40
|
-
|
|
41
|
-
for root, dirs, files in os.walk(abs_target):
|
|
42
|
-
dirs[:] = [
|
|
43
|
-
d for d in dirs
|
|
44
|
-
if not d.startswith(".")
|
|
45
|
-
and not ki_utils.should_exclude(d, os.path.relpath(os.path.join(root, d), project_root), exclude_patterns)
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
for f in files:
|
|
49
|
-
if f.startswith(".") or any(f.endswith(ext) for ext in ignored_exts):
|
|
50
|
-
continue
|
|
51
|
-
|
|
52
|
-
full_path = os.path.abspath(os.path.join(root, f))
|
|
53
|
-
rel_to_project = os.path.relpath(full_path, project_root).replace("\\", "/")
|
|
54
|
-
if ki_utils.should_exclude(f, rel_to_project, exclude_patterns):
|
|
55
|
-
continue
|
|
56
|
-
|
|
57
|
-
if full_path in mapped_paths:
|
|
58
|
-
continue
|
|
59
|
-
|
|
60
|
-
is_parent_mapped = False
|
|
61
|
-
for mapped in mapped_paths:
|
|
62
|
-
if os.path.isdir(mapped):
|
|
63
|
-
if full_path.startswith(os.path.join(mapped, "")):
|
|
64
|
-
is_parent_mapped = True
|
|
65
|
-
break
|
|
66
|
-
|
|
67
|
-
if not is_parent_mapped:
|
|
68
|
-
unmapped.append(rel_to_project)
|
|
69
|
-
|
|
70
|
-
return sorted(unmapped)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def find_unmapped_files(target_path: str):
|
|
74
|
-
project_root = ki_utils.get_project_root()
|
|
75
|
-
doc_config = ki_utils.get_doc_config()
|
|
76
|
-
|
|
77
|
-
if not project_root or not doc_config:
|
|
78
|
-
print("Error: Could not resolve project root or doc_config.json")
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
abs_target = os.path.abspath(os.path.join(project_root, target_path))
|
|
82
|
-
if not os.path.exists(abs_target):
|
|
83
|
-
print(f"Error: Target path '{target_path}' does not exist.")
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
unmapped = get_unmapped_files(target_path)
|
|
87
|
-
if unmapped:
|
|
88
|
-
print(f"### Unmapped Files in '{target_path}':")
|
|
89
|
-
for f in unmapped:
|
|
90
|
-
print(f"- {f}")
|
|
91
|
-
else:
|
|
92
|
-
print(f"All files in '{target_path}' are already mapped in doc_config.json.")
|
|
93
|
-
|
|
94
|
-
if __name__ == "__main__":
|
|
95
|
-
if sys.platform == "win32":
|
|
96
|
-
sys.stdout.reconfigure(encoding="utf-8")
|
|
97
|
-
parser = argparse.ArgumentParser(description="Find files in a directory not covered by any KI in doc_config.json")
|
|
98
|
-
parser.add_argument("path", help="Relative path from project root to scan")
|
|
99
|
-
args = parser.parse_args()
|
|
100
|
-
|
|
101
|
-
find_unmapped_files(args.path)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/finalize_ki_scaffolds.py
RENAMED
|
File without changes
|
{ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/generate_dir_index.py
RENAMED
|
File without changes
|
{ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/generate_ki_scaffolds.py
RENAMED
|
File without changes
|
|
File without changes
|
{ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/ki_dependency_analyzer.py
RENAMED
|
File without changes
|
{ki_manager-2.3.1.dev1 → ki_manager-2.3.2.dev2}/src/ki_manager/scripts/ki_graph_visualize.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|