intentic-ike 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ike/__init__.py +3 -0
- ike/__main__.py +3 -0
- ike/cli.py +56 -0
- ike/commands/__init__.py +2 -0
- ike/commands/doctor_cmd.py +33 -0
- ike/commands/fetch_cmd.py +22 -0
- ike/commands/flag_stale_cmd.py +19 -0
- ike/commands/index_cmd.py +19 -0
- ike/commands/init_cmd.py +37 -0
- ike/commands/lint_cmd.py +19 -0
- ike/commands/list_cmd.py +17 -0
- ike/commands/migrate_mcp_cmd.py +30 -0
- ike/commands/query_cmd.py +20 -0
- ike/commands/route_cmd.py +19 -0
- ike/commands/serve_cmd.py +17 -0
- ike/commands/write_cmd.py +38 -0
- ike/core/__init__.py +0 -0
- ike/core/artifacts.py +146 -0
- ike/core/doctor.py +167 -0
- ike/core/engine.py +156 -0
- ike/core/fetcher.py +54 -0
- ike/core/index.py +245 -0
- ike/core/init.py +52 -0
- ike/core/linter.py +164 -0
- ike/core/migrate.py +50 -0
- ike/core/parser.py +317 -0
- ike/core/serve.py +32 -0
- ike/core/types.py +82 -0
- ike/core/writer.py +210 -0
- ike/mcp_server.py +93 -0
- intentic_ike-0.2.0.dist-info/METADATA +27 -0
- intentic_ike-0.2.0.dist-info/RECORD +36 -0
- intentic_ike-0.2.0.dist-info/WHEEL +5 -0
- intentic_ike-0.2.0.dist-info/entry_points.txt +2 -0
- intentic_ike-0.2.0.dist-info/licenses/LICENSE +21 -0
- intentic_ike-0.2.0.dist-info/top_level.txt +1 -0
ike/__init__.py
ADDED
ike/__main__.py
ADDED
ike/cli.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""ike CLI — dynamic command loading from ike/commands/.
|
|
2
|
+
|
|
3
|
+
Each *_cmd.py in ike/commands/ exports a Click command as `cmd`.
|
|
4
|
+
Commands are auto-discovered at CLI startup.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import importlib
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
from .core.engine import Engine
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DynamicGroup(click.Group):
|
|
18
|
+
"""Click group that auto-discovers commands from ike/commands/*_cmd.py."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, *args, **kwargs):
|
|
21
|
+
super().__init__(*args, **kwargs)
|
|
22
|
+
self._load_commands()
|
|
23
|
+
|
|
24
|
+
def _load_commands(self) -> None:
|
|
25
|
+
commands_dir = Path(__file__).parent / "commands"
|
|
26
|
+
if not commands_dir.exists():
|
|
27
|
+
return
|
|
28
|
+
for f in sorted(commands_dir.glob("*_cmd.py")):
|
|
29
|
+
name = f.stem.removesuffix("_cmd").replace("_", "-")
|
|
30
|
+
try:
|
|
31
|
+
mod = importlib.import_module(f"ike.commands.{f.stem}")
|
|
32
|
+
if hasattr(mod, "cmd"):
|
|
33
|
+
self.add_command(mod.cmd, name)
|
|
34
|
+
except Exception:
|
|
35
|
+
pass # Graceful: skip broken commands
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.group(cls=DynamicGroup)
|
|
39
|
+
@click.version_option(package_name="intentic-ike")
|
|
40
|
+
@click.option(
|
|
41
|
+
"--kb-root",
|
|
42
|
+
type=click.Path(),
|
|
43
|
+
default=None,
|
|
44
|
+
envvar="IKE_KB_ROOT",
|
|
45
|
+
help="KB root directory (default: ~/intentic-kb)",
|
|
46
|
+
)
|
|
47
|
+
@click.pass_context
|
|
48
|
+
def cli(ctx: click.Context, kb_root: str | None) -> None:
|
|
49
|
+
"""ike — intentic Knowledge Engine."""
|
|
50
|
+
ctx.ensure_object(dict)
|
|
51
|
+
kb = Path(kb_root).expanduser() if kb_root else None
|
|
52
|
+
ctx.obj["kb_root"] = kb
|
|
53
|
+
try:
|
|
54
|
+
ctx.obj["engine"] = Engine(kb_root=kb)
|
|
55
|
+
except Exception:
|
|
56
|
+
ctx.obj["engine"] = None
|
ike/commands/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""ike doctor — auto-fix missing frontmatter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.option("--yes", "auto_fix", is_flag=True, help="Apply all fixes without prompting")
|
|
12
|
+
@click.option(
|
|
13
|
+
"--kb-root",
|
|
14
|
+
type=click.Path(exists=True),
|
|
15
|
+
default=None,
|
|
16
|
+
help="KB root (defaults to --kb-root from parent group or IKE_KB_ROOT)",
|
|
17
|
+
)
|
|
18
|
+
@click.pass_context
|
|
19
|
+
def cmd(ctx: click.Context, auto_fix: bool, kb_root: str | None) -> None:
|
|
20
|
+
"""Auto-fix missing frontmatter in KB documents."""
|
|
21
|
+
from ike.core.doctor import run_doctor
|
|
22
|
+
|
|
23
|
+
if kb_root:
|
|
24
|
+
kb = Path(kb_root).resolve()
|
|
25
|
+
else:
|
|
26
|
+
kb = ctx.obj.get("kb_root")
|
|
27
|
+
if kb is None:
|
|
28
|
+
kb = Path.home() / "intentic-kb"
|
|
29
|
+
|
|
30
|
+
fixed = run_doctor(kb, auto_fix=auto_fix)
|
|
31
|
+
click.echo(f"\nFixed {fixed} file(s).")
|
|
32
|
+
if fixed > 0:
|
|
33
|
+
click.echo("Run `ike lint` to verify.")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Load KB content (entire file or specific section)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.argument("file_path")
|
|
10
|
+
@click.option("--section", default=None, help="Section ID to fetch")
|
|
11
|
+
@click.pass_context
|
|
12
|
+
def cmd(ctx: click.Context, file_path: str, section: str | None) -> None:
|
|
13
|
+
"""Load KB content (entire file or specific section)."""
|
|
14
|
+
try:
|
|
15
|
+
result = ctx.obj["engine"].fetch(file_path, section)
|
|
16
|
+
click.echo(result.content)
|
|
17
|
+
except KeyError as e:
|
|
18
|
+
click.echo(f"Error: {e}", err=True)
|
|
19
|
+
raise SystemExit(1)
|
|
20
|
+
except FileNotFoundError:
|
|
21
|
+
click.echo(f"Error: File not found: {file_path}", err=True)
|
|
22
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Flag a section as stale."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command("flag-stale")
|
|
9
|
+
@click.argument("file_path")
|
|
10
|
+
@click.option("--section", required=True, help="Section ID to flag")
|
|
11
|
+
@click.option("--reason", required=True, help="Why the section is stale")
|
|
12
|
+
@click.pass_context
|
|
13
|
+
def cmd(ctx: click.Context, file_path: str, section: str, reason: str) -> None:
|
|
14
|
+
"""Flag a section as stale."""
|
|
15
|
+
result = ctx.obj["engine"].flag_stale(file_path, section, reason)
|
|
16
|
+
if not result.success:
|
|
17
|
+
click.echo(f"Error: {result.error}", err=True)
|
|
18
|
+
raise SystemExit(1)
|
|
19
|
+
click.echo(f"Flagged {file_path}#{section} as stale: {reason}")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Build or rebuild the search index."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.option("--rebuild", is_flag=True, help="Full rebuild (drop + reindex)")
|
|
10
|
+
@click.pass_context
|
|
11
|
+
def cmd(ctx: click.Context, rebuild: bool) -> None:
|
|
12
|
+
"""Build or rebuild the search index."""
|
|
13
|
+
engine = ctx.obj["engine"]
|
|
14
|
+
if rebuild:
|
|
15
|
+
n = engine.rebuild_index()
|
|
16
|
+
click.echo(f"Index rebuilt: {n} chunks indexed.")
|
|
17
|
+
else:
|
|
18
|
+
engine.index.ensure_fresh(engine.parser)
|
|
19
|
+
click.echo("Index is fresh.")
|
ike/commands/init_cmd.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""ike init — bootstrap ike for a repository."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command()
|
|
11
|
+
@click.option(
|
|
12
|
+
"--kb-root",
|
|
13
|
+
type=click.Path(exists=True),
|
|
14
|
+
required=True,
|
|
15
|
+
help="Path to the knowledge base directory",
|
|
16
|
+
)
|
|
17
|
+
@click.pass_context
|
|
18
|
+
def cmd(ctx: click.Context, kb_root: str) -> None:
|
|
19
|
+
"""Bootstrap ike for a repository. Scans docs and generates discovery artifacts."""
|
|
20
|
+
from ike.core.init import run_init
|
|
21
|
+
|
|
22
|
+
kb = Path(kb_root).resolve()
|
|
23
|
+
cwd = Path.cwd()
|
|
24
|
+
|
|
25
|
+
result = run_init(kb, cwd)
|
|
26
|
+
|
|
27
|
+
click.echo(f"Found {result['total']} markdown files in {kb}")
|
|
28
|
+
click.echo()
|
|
29
|
+
click.echo("Quality Report:")
|
|
30
|
+
click.echo(f" {result['ready']} ready")
|
|
31
|
+
click.echo(f" {result['fixable']} fixable (run `ike doctor` to fix)")
|
|
32
|
+
click.echo(f" {result['needs_review']} need review")
|
|
33
|
+
click.echo()
|
|
34
|
+
for a in result["artifacts"]:
|
|
35
|
+
click.echo(f"Created: {a}")
|
|
36
|
+
click.echo()
|
|
37
|
+
click.echo("Restart your AI tool to activate ike MCP.")
|
ike/commands/lint_cmd.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Run consistency checks on the KB."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.option("--freshness", type=int, default=None, help="Flag docs older than N days")
|
|
10
|
+
@click.pass_context
|
|
11
|
+
def cmd(ctx: click.Context, freshness: int | None) -> None:
|
|
12
|
+
"""Run consistency checks on the KB."""
|
|
13
|
+
findings = ctx.obj["engine"].lint(freshness)
|
|
14
|
+
if not findings:
|
|
15
|
+
click.echo("No issues found.")
|
|
16
|
+
return
|
|
17
|
+
for f in findings:
|
|
18
|
+
click.echo(f"[{f.severity}] {f.file_path}: {f.check} — {f.message}")
|
|
19
|
+
click.echo(f"\n{len(findings)} issue(s) found.")
|
ike/commands/list_cmd.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""List all KB documents and sections."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command("list")
|
|
9
|
+
@click.option("--type", "knowledge_type", default=None, help="Filter by type")
|
|
10
|
+
@click.pass_context
|
|
11
|
+
def cmd(ctx: click.Context, knowledge_type: str | None) -> None:
|
|
12
|
+
"""List all KB documents and sections."""
|
|
13
|
+
chunks = ctx.obj["engine"].list_docs(knowledge_type)
|
|
14
|
+
for c in chunks:
|
|
15
|
+
section = f"#{c.section_id}" if c.section_id else ""
|
|
16
|
+
click.echo(f"{c.file_path}{section} ({c.token_count} tokens)")
|
|
17
|
+
click.echo(f"\n{len(chunks)} section(s).")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""ike migrate-mcp — migrate .mcp.json to portable pattern."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command("migrate-mcp")
|
|
12
|
+
@click.option("--dry-run", is_flag=True, help="Show changes without applying")
|
|
13
|
+
def cmd(dry_run: bool) -> None:
|
|
14
|
+
"""Migrate .mcp.json from absolute paths to portable uvx/pipx pattern."""
|
|
15
|
+
from ike.core.migrate import run_migrate_mcp
|
|
16
|
+
|
|
17
|
+
result = run_migrate_mcp(Path.cwd(), dry_run=dry_run)
|
|
18
|
+
|
|
19
|
+
if not result["changed"]:
|
|
20
|
+
click.echo(f"No migration needed: {result.get('reason', 'unknown')}")
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
if dry_run:
|
|
24
|
+
click.echo("DRY RUN — would change:")
|
|
25
|
+
click.echo(f" Old: {json.dumps(result['old'], indent=2)}")
|
|
26
|
+
click.echo(f" New: {json.dumps(result['new'], indent=2)}")
|
|
27
|
+
else:
|
|
28
|
+
click.echo("Migrated .mcp.json:")
|
|
29
|
+
click.echo(f" Old command: {result['old'].get('command')}")
|
|
30
|
+
click.echo(f" New command: {result['new'].get('command')}")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Route + fetch in one step."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.argument("query_text")
|
|
10
|
+
@click.option("--depth", type=click.Choice(["shallow", "deep"]), default="shallow")
|
|
11
|
+
@click.option("--limit", default=5)
|
|
12
|
+
@click.pass_context
|
|
13
|
+
def cmd(ctx: click.Context, query_text: str, depth: str, limit: int) -> None:
|
|
14
|
+
"""Route + fetch in one step."""
|
|
15
|
+
results = ctx.obj["engine"].query_and_fetch(query_text, depth, limit)
|
|
16
|
+
for r in results:
|
|
17
|
+
section_label = f"#{r.section_id}" if r.section_id else ""
|
|
18
|
+
click.echo(f"--- {r.file_path}{section_label} ({r.token_count} tokens) ---")
|
|
19
|
+
click.echo(r.content)
|
|
20
|
+
click.echo()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Route a query to relevant KB sections."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from dataclasses import asdict
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.argument("query_text")
|
|
13
|
+
@click.option("--limit", default=10, help="Max results")
|
|
14
|
+
@click.option("--type", "knowledge_type", default=None, help="Filter by type")
|
|
15
|
+
@click.pass_context
|
|
16
|
+
def cmd(ctx: click.Context, query_text: str, limit: int, knowledge_type: str | None) -> None:
|
|
17
|
+
"""Find relevant KB sections. Returns paths + token counts."""
|
|
18
|
+
result = ctx.obj["engine"].route(query_text, limit, knowledge_type)
|
|
19
|
+
click.echo(json.dumps(asdict(result), indent=2))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""ike serve — start the MCP server.
|
|
2
|
+
|
|
3
|
+
CRITICAL: No top-level imports of fastmcp or ike.mcp_server.
|
|
4
|
+
CRITICAL: No print(), sys.stdout.write(), or click.echo() — stdout is for JSON-RPC.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
def cmd() -> None:
|
|
14
|
+
"""Start the ike MCP server (stdio transport)."""
|
|
15
|
+
from ike.core.serve import run_serve
|
|
16
|
+
|
|
17
|
+
run_serve()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Modify KB content. Content is read from stdin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command("write")
|
|
11
|
+
@click.option("--file", "file_path", required=True, help="File to modify")
|
|
12
|
+
@click.option("--mode", type=click.Choice(["append", "replace"]), required=True)
|
|
13
|
+
@click.option("--section", default=None, help="Section ID (required for replace)")
|
|
14
|
+
@click.option("--agent-id", default="manual", help="Agent identifier for git commit")
|
|
15
|
+
@click.option("--no-commit", is_flag=True, help="Skip git commit")
|
|
16
|
+
@click.pass_context
|
|
17
|
+
def cmd(
|
|
18
|
+
ctx: click.Context,
|
|
19
|
+
file_path: str,
|
|
20
|
+
mode: str,
|
|
21
|
+
section: str | None,
|
|
22
|
+
agent_id: str,
|
|
23
|
+
no_commit: bool,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Modify KB content. Content is read from stdin."""
|
|
26
|
+
content = sys.stdin.read()
|
|
27
|
+
if not content.strip():
|
|
28
|
+
click.echo("Error: No content provided via stdin", err=True)
|
|
29
|
+
raise SystemExit(1)
|
|
30
|
+
|
|
31
|
+
result = ctx.obj["engine"].write(
|
|
32
|
+
file_path, content, mode, section, agent_id, not no_commit
|
|
33
|
+
)
|
|
34
|
+
if not result.success:
|
|
35
|
+
click.echo(f"Error: {result.error}", err=True)
|
|
36
|
+
raise SystemExit(1)
|
|
37
|
+
|
|
38
|
+
click.echo(f"Written to {result.file_path} (mode={result.mode}, committed={result.committed})")
|
ike/core/__init__.py
ADDED
|
File without changes
|
ike/core/artifacts.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Generate discovery artifacts for AI tools (.mcp.json, AGENTS.md, etc.)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shutil
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _detect_runner() -> tuple[str, list[str]]:
|
|
11
|
+
"""Detect best available runner: uvx > pipx > python3 -m."""
|
|
12
|
+
if shutil.which("uvx"):
|
|
13
|
+
return "uvx", ["--from", "intentic-ike[mcp]", "ike", "serve"]
|
|
14
|
+
if shutil.which("pipx"):
|
|
15
|
+
return "pipx", ["run", "intentic-ike[mcp]", "serve"]
|
|
16
|
+
return "ike", ["serve"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def generate_mcp_json(target: Path) -> Path:
|
|
20
|
+
"""Generate or merge ike entry into .mcp.json."""
|
|
21
|
+
mcp_path = target / ".mcp.json"
|
|
22
|
+
runner_cmd, runner_args = _detect_runner()
|
|
23
|
+
|
|
24
|
+
ike_entry = {
|
|
25
|
+
"command": runner_cmd,
|
|
26
|
+
"args": runner_args,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if mcp_path.exists():
|
|
30
|
+
data = json.loads(mcp_path.read_text(encoding="utf-8"))
|
|
31
|
+
else:
|
|
32
|
+
data = {}
|
|
33
|
+
|
|
34
|
+
data.setdefault("mcpServers", {})
|
|
35
|
+
data["mcpServers"]["ike"] = ike_entry
|
|
36
|
+
|
|
37
|
+
mcp_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
38
|
+
return mcp_path
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def generate_agents_md(kb_root: Path, target: Path) -> Path:
|
|
42
|
+
"""Generate AGENTS.md for AI tool discovery."""
|
|
43
|
+
agents_path = target / "AGENTS.md"
|
|
44
|
+
kb_name = kb_root.name
|
|
45
|
+
|
|
46
|
+
content = f"""# ike — intentic Knowledge Engine
|
|
47
|
+
|
|
48
|
+
This repository uses **ike** to make its knowledge base queryable by AI tools.
|
|
49
|
+
ike indexes the `{kb_name}/` directory and serves content via 2-step retrieval.
|
|
50
|
+
|
|
51
|
+
## How to query (CLI)
|
|
52
|
+
|
|
53
|
+
**IMPORTANT:** `ike route` returns file paths relative to the KB root. Use those
|
|
54
|
+
EXACT paths with `ike fetch` — do NOT prepend the KB directory name.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Step 1: Find relevant sections
|
|
58
|
+
ike route "your question"
|
|
59
|
+
# Returns: {{"file_path": "architecture/auth.md", "section_id": "rate-limiting", ...}}
|
|
60
|
+
|
|
61
|
+
# Step 2: Fetch using the EXACT file_path from route results
|
|
62
|
+
ike fetch architecture/auth.md --section rate-limiting
|
|
63
|
+
|
|
64
|
+
# Or combine both steps:
|
|
65
|
+
ike query "your question" --depth deep
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### All CLI commands
|
|
69
|
+
|
|
70
|
+
| Command | Purpose |
|
|
71
|
+
|---------|---------|
|
|
72
|
+
| `ike route "query"` | Find relevant sections (returns paths + token counts) |
|
|
73
|
+
| `ike fetch <path>` | Load entire file (path from route results) |
|
|
74
|
+
| `ike fetch <path> --section <id>` | Load specific section |
|
|
75
|
+
| `ike query "text" --depth deep` | Route + fetch combined |
|
|
76
|
+
| `ike lint` | Check KB consistency |
|
|
77
|
+
| `ike list` | List all indexed sections with paths |
|
|
78
|
+
|
|
79
|
+
## How to query (MCP)
|
|
80
|
+
|
|
81
|
+
For MCP-capable tools (Claude Code, Cursor, Windsurf, Zed), the server is
|
|
82
|
+
configured in `.mcp.json`. Available tools:
|
|
83
|
+
|
|
84
|
+
| Tool | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `route(query, limit)` | Find relevant sections (~100 tokens) |
|
|
87
|
+
| `fetch(file_path, section_id)` | Load content (use paths from route) |
|
|
88
|
+
| `query(query_text, depth)` | Route + fetch in one step |
|
|
89
|
+
| `lint(freshness_days)` | Check KB health |
|
|
90
|
+
|
|
91
|
+
## Example workflow
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
$ ike route "authentication"
|
|
95
|
+
architecture/auth.md#api-keys (score: 3, 120 tokens)
|
|
96
|
+
architecture/auth.md#oauth2-flow (score: 2, 150 tokens)
|
|
97
|
+
architecture/auth.md#rate-limiting (score: 1, 80 tokens)
|
|
98
|
+
|
|
99
|
+
$ ike fetch architecture/auth.md --section rate-limiting
|
|
100
|
+
## Rate Limiting
|
|
101
|
+
| Tier | Requests/min | Burst |
|
|
102
|
+
...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Knowledge Base
|
|
106
|
+
|
|
107
|
+
- **Root:** `{kb_name}/`
|
|
108
|
+
- **Paths:** All file_paths in ike are relative to this root
|
|
109
|
+
- **Format:** Markdown with YAML frontmatter (title, domain, summary)
|
|
110
|
+
"""
|
|
111
|
+
agents_path.write_text(content, encoding="utf-8")
|
|
112
|
+
return agents_path
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def generate_cursorrules(target: Path) -> Path:
|
|
116
|
+
"""Generate or append ike snippet to .cursorrules."""
|
|
117
|
+
rules_path = target / ".cursorrules"
|
|
118
|
+
|
|
119
|
+
snippet = """
|
|
120
|
+
# ike Knowledge Engine
|
|
121
|
+
# Use `ike route "query"` to find relevant KB sections, then `ike fetch <path>` to load content.
|
|
122
|
+
# MCP tools are also available if configured in .mcp.json.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
if rules_path.exists():
|
|
126
|
+
existing = rules_path.read_text(encoding="utf-8")
|
|
127
|
+
if "ike" not in existing:
|
|
128
|
+
rules_path.write_text(existing.rstrip() + "\n" + snippet, encoding="utf-8")
|
|
129
|
+
else:
|
|
130
|
+
rules_path.write_text(snippet.lstrip(), encoding="utf-8")
|
|
131
|
+
return rules_path
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def generate_llms_txt(kb_root: Path, target: Path) -> Path:
|
|
135
|
+
"""Generate llms.txt listing KB structure."""
|
|
136
|
+
llms_path = target / "llms.txt"
|
|
137
|
+
lines = [f"# {kb_root.name} Knowledge Base", "", "## Documents", ""]
|
|
138
|
+
|
|
139
|
+
for md in sorted(kb_root.rglob("*.md")):
|
|
140
|
+
if md.name.startswith("."):
|
|
141
|
+
continue
|
|
142
|
+
rel = md.relative_to(kb_root)
|
|
143
|
+
lines.append(f"- {rel}")
|
|
144
|
+
|
|
145
|
+
llms_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
146
|
+
return llms_path
|