fix(git): give each ktx project its own git repo root

A ktx project assumes its config dir is its own git working-tree root: writes,
session worktrees, squash-merges, and reindex scans all resolve relative to it.
GitService.initialize() gated on checkIsRepo() (IN_TREE), which is also
satisfied by an *enclosing* repository — so a project nested inside another git
working tree silently operated against the outer repo. Worktree/ingest writes
landed at the outer root (e.g. <outer>/wiki/global/) while reindex scanned
<projectDir>/wiki/global/, so the wiki was seeded but never indexed:
wiki_search returned nothing and knowledge_pages stayed empty, with no error.
Semantic-layer and raw-sources had the same divergence.

Gate initialization on checkIsRepo('root') instead: require the repo root to be
the config dir itself, and initialize a dedicated repository there when it is
not (logging clearly when nesting inside an existing repo). This restores the
one-repo-per-project invariant at the shared git layer, fixing all artifacts at
once, and keeps ktx's commits out of the enclosing repository.
This commit is contained in:
Andrey Avtomonov 2026-06-09 12:14:07 +02:00
parent 41acc5959c
commit 1a6da14f62
4 changed files with 130 additions and 7 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';
@ -94,15 +94,29 @@ export class GitService {
private async initialize(): Promise<void> {
try {
// Check if already initialized
const isRepo = await this.git.checkIsRepo();
// The ktx store assumes configDir is its own git working-tree root: writes, session
// worktrees, squash-merges, and reindex scans are all resolved relative to it. The
// default checkIsRepo() (IN_TREE) is also satisfied by an *enclosing* repository, so a
// project nested inside another git working tree would silently operate against that
// outer repo — ingest-written files land at the outer root while reindex scans configDir,
// leaving e.g. the wiki seeded but unindexed. Require configDir to be the repo root
// itself; otherwise initialize a dedicated repository here.
const isOwnRepoRoot = await this.git.checkIsRepo(CheckRepoActions.IS_REPO_ROOT);
if (!isRepo) {
if (!isOwnRepoRoot) {
const insideEnclosingRepo = await this.git.checkIsRepo(CheckRepoActions.IN_TREE);
await this.git.init();
const gitConfig = this.config.git;
await this.git.addConfig('user.name', gitConfig.userName);
await this.git.addConfig('user.email', gitConfig.userEmail);
this.logger.log('Initialized git repository');
if (insideEnclosingRepo) {
this.logger.log(
`Initialized a dedicated ktx git repository at ${this.configDir} (nested inside an existing ` +
'git repository); ktx commits stay in this repository and do not touch the outer one.',
);
} else {
this.logger.log('Initialized git repository');
}
}
// Keep any auto-maintenance triggered by writes in-process. Detached maintenance can