fix(cli): own a dedicated git repo at the project dir when nested in an enclosing repo (#282)

GitService.initialize() used checkIsRepo(), which is true whenever the project
dir sits anywhere inside a git working tree. So when a ktx project lived in a
subdirectory of an enclosing repo, ktx skipped `git init` and silently adopted
the enclosing repo as its store.

Every ktx relative path assumes the project dir IS the working-tree root. During
ingest, wiki/SL pages are written through a session worktree (whose root is the
worktree dir, so the page is recorded at repo-relative `wiki/global/<key>.md`)
and then squash-merged into the main worktree. With an adopted enclosing repo,
the main worktree's root is the enclosing git root, so the merge wrote the page
to `<gitRoot>/wiki/global/` — outside the project dir. reindex scans
`<projectDir>/wiki/global/`, found nothing, and wiki_search silently returned
empty (knowledge_pages = 0) even though ingest reported success.

Detect the project dir's own root with checkIsRepo(IS_REPO_ROOT) and initialize
a dedicated repo there unless the project dir is already a repo root. This keeps
adopting a user-created repo when the project dir IS that repo's root, fixes the
silent wiki/SL/memory divergence at its source for every writer, and stops ktx
from committing its scaffold into the user's enclosing repo.

Regression tests cover both layers: a project nested in an enclosing repo gets
its own .git (and the enclosing repo stays untouched), and a wiki page written
through a session worktree + squash-merge lands in the project dir and is
discovered by reindex.
This commit is contained in:
Andrey Avtomonov 2026-06-09 23:37:24 +02:00 committed by GitHub
parent 65de75ebd7
commit fd18caa26a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 101 additions and 5 deletions

View file

@ -1,6 +1,6 @@
import { promises as fs } from 'node:fs';
import { dirname, join } from 'node:path';
import type { SimpleGit } from 'simple-git';
import { CheckRepoActions, type SimpleGit } from 'simple-git';
import { noopLogger, resolveConfigDir, type KtxCoreConfig, type KtxLogger } from './config.js';
import { createSimpleGit } from './git-env.js';
@ -98,10 +98,15 @@ export class GitService {
private async initialize(): Promise<void> {
try {
// Check if already initialized
const isRepo = await this.git.checkIsRepo();
// Adopt an existing repo ONLY when this directory is itself that repo's root.
// When it sits below an enclosing repo, a plain checkIsRepo() is true and ktx
// would silently piggyback on the enclosing tree — but every ktx relative path
// (file-store writes, session worktrees, squash-merges, reindex scans) assumes
// this directory IS the working-tree root. So treat "inside an enclosing repo"
// the same as "no repo" and initialize a dedicated repo rooted here.
const isRepoRoot = await this.git.checkIsRepo(CheckRepoActions.IS_REPO_ROOT);
if (!isRepo) {
if (!isRepoRoot) {
await this.git.init();
this.logger.log('Initialized git repository');
}