Add AI knowledge exclude list

This commit is contained in:
juvdiaz 2026-06-29 21:05:30 -06:00
parent af75f2c608
commit 3c24b7fbc4
4 changed files with 26 additions and 5 deletions

View File

@ -1113,7 +1113,8 @@ Build the local homelab knowledge index separately from the main deployment:
```
The index is built from the non-secret source list in
`infra/ai/knowledge-sources.txt` and defaults to `/data/homelab-ai/index`.
`infra/ai/knowledge-sources.txt`, skips paths in
`infra/ai/knowledge-excludes.txt`, and defaults to `/data/homelab-ai/index`.
Doctor commands use it as extra context when it exists, but infrastructure
deployment does not depend on it.
Use `ask` as the runbook and command finder. It returns the closest indexed

View File

@ -1113,7 +1113,8 @@ Build the local homelab knowledge index separately from the main deployment:
```
The index is built from the non-secret source list in
`infra/ai/knowledge-sources.txt` and defaults to `/data/homelab-ai/index`.
`infra/ai/knowledge-sources.txt`, skips paths in
`infra/ai/knowledge-excludes.txt`, and defaults to `/data/homelab-ai/index`.
Doctor commands use it as extra context when it exists, but infrastructure
deployment does not depend on it.
Use `ask` as the runbook and command finder. It returns the closest indexed

View File

@ -0,0 +1,6 @@
# Files and globs excluded from the local homelab AI helper.
# Keep inactive reference material out of answers about the active lab.
archive/**
labs/**
apps/arr-stack/**
apps/mlops-platform/**

View File

@ -16,6 +16,7 @@ from collections import Counter
REPO_ROOT = pathlib.Path(__file__).resolve().parents[1]
DEFAULT_SOURCES_FILE = REPO_ROOT / "infra" / "ai" / "knowledge-sources.txt"
DEFAULT_EXCLUDES_FILE = REPO_ROOT / "infra" / "ai" / "knowledge-excludes.txt"
DEFAULT_INDEX_DIR = pathlib.Path(os.environ.get("LAB_AI_KNOWLEDGE_INDEX_DIR", "/data/homelab-ai/index"))
TOKEN_RE = re.compile(r"[A-Za-z0-9_./:-]{2,}")
MAX_FILE_BYTES = 512 * 1024
@ -54,7 +55,11 @@ def path_is_secret(path: pathlib.Path) -> bool:
return any(marker in lowered for marker in secret_markers)
def candidate_files(patterns: list[str]) -> list[pathlib.Path]:
def path_matches_any(rel_path: str, patterns: list[str]) -> bool:
return any(fnmatch.fnmatch(rel_path, pattern) for pattern in patterns)
def candidate_files(patterns: list[str], exclude_patterns: list[str]) -> list[pathlib.Path]:
files: set[pathlib.Path] = set()
all_files = [path for path in REPO_ROOT.rglob("*") if path.is_file()]
@ -69,7 +74,12 @@ def candidate_files(patterns: list[str]) -> list[pathlib.Path]:
if path.is_file():
files.add(path)
return sorted(path for path in files if not path_is_secret(path))
return sorted(
path
for path in files
if not path_is_secret(path)
and not path_matches_any(path.relative_to(REPO_ROOT).as_posix(), exclude_patterns)
)
def read_text(path: pathlib.Path) -> str | None:
@ -155,6 +165,7 @@ def build_index(files: list[pathlib.Path]) -> dict[str, object]:
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--sources-file", type=pathlib.Path, default=DEFAULT_SOURCES_FILE)
parser.add_argument("--excludes-file", type=pathlib.Path, default=DEFAULT_EXCLUDES_FILE)
parser.add_argument("--index-dir", type=pathlib.Path, default=DEFAULT_INDEX_DIR)
args = parser.parse_args()
@ -163,7 +174,8 @@ def main() -> int:
return 1
patterns = load_patterns(args.sources_file)
files = candidate_files(patterns)
exclude_patterns = load_patterns(args.excludes_file) if args.excludes_file.is_file() else []
files = candidate_files(patterns, exclude_patterns)
index = build_index(files)
args.index_dir.mkdir(parents=True, exist_ok=True)
@ -174,6 +186,7 @@ def main() -> int:
json.dumps(
{
"sources_file": str(args.sources_file.relative_to(REPO_ROOT)),
"excludes_file": str(args.excludes_file.relative_to(REPO_ROOT)) if args.excludes_file.is_file() else None,
"file_count": len(files),
"chunk_count": index["chunk_count"],
"repo_root": str(REPO_ROOT),