mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-25 08:48:08 +02:00
fix(ingest): honor storage.git.auto_commit and memory.auto_commit
Both documented flags were read only for status display; every ingest path squash-committed to main unconditionally, so setting either to false was a silent no-op (the reported symptom: 'Memory ingest (external_ingest): ...' commits despite memory.auto_commit: false). Gate the commit at the squash-merge onto main — the one point where ingest work becomes a permanent commit (intermediate session-worktree commits must still happen for the squash to collapse). When auto-commit is off, apply the squash to main's working tree and leave it staged instead of committing, so the run is never silently discarded: - GitService.stageSquashMergeIntoMain: shares the merge core with squashMergeIntoMain but stops before committing and returns the staged tree SHA (a valid diff/read ref). - memory.auto_commit gates MemoryAgentService (its DB writes are eager, so the staged files stay consistent); the commit-message job is skipped. - storage.git.auto_commit gates IngestBundleRunner; the wiki index is reconciled from the staged tree via the existing syncFromCommit (git diff/show accept a write-tree ref), and SL reindex already reads from files. Config descriptions now state precisely what each flag gates and the staged semantics when false.
This commit is contained in:
parent
1a6da14f62
commit
a02fcab487
15 changed files with 303 additions and 43 deletions
|
|
@ -40,6 +40,7 @@ interface BuiltMocks {
|
|||
slValidator: any;
|
||||
toolsetFactory: any;
|
||||
logger: any;
|
||||
autoCommit: boolean;
|
||||
}
|
||||
|
||||
const buildMocks = (overrides: Partial<BuiltMocks> = {}): BuiltMocks => {
|
||||
|
|
@ -111,6 +112,9 @@ const buildMocks = (overrides: Partial<BuiltMocks> = {}): BuiltMocks => {
|
|||
gitService: {
|
||||
revParseHead: vi.fn().mockResolvedValue('basesha'),
|
||||
squashMergeIntoMain: vi.fn().mockResolvedValue({ ok: true, squashSha: 'cafebabe', touchedPaths: ['a.yaml'] }),
|
||||
stageSquashMergeIntoMain: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ ok: true, touchedPaths: ['a.yaml'], stagedTree: 'deadbeeftree' }),
|
||||
},
|
||||
lockingService: {
|
||||
withLock: vi.fn().mockImplementation((_key: string, fn: () => Promise<unknown>) => fn()),
|
||||
|
|
@ -134,6 +138,7 @@ const buildMocks = (overrides: Partial<BuiltMocks> = {}): BuiltMocks => {
|
|||
}),
|
||||
},
|
||||
logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
||||
autoCommit: true,
|
||||
};
|
||||
|
||||
return { ...defaults, ...overrides };
|
||||
|
|
@ -151,6 +156,7 @@ const buildService = (mocks: BuiltMocks): MemoryAgentService =>
|
|||
llm: {
|
||||
memoryIngestionModel: mocks.appSettings.settings.llm.memoryIngestionModel,
|
||||
},
|
||||
autoCommit: mocks.autoCommit,
|
||||
},
|
||||
promptService: mocks.prompt,
|
||||
skillsRegistry: mocks.skillsRegistry,
|
||||
|
|
@ -242,6 +248,26 @@ describe('MemoryAgentService.ingest — session-branch orchestration', () => {
|
|||
expect(result.commitHash).toBe('cafebabe');
|
||||
});
|
||||
|
||||
it('with auto_commit disabled, stages the session on main without committing or enqueuing a note', async () => {
|
||||
const mocks = buildMocks({ autoCommit: false });
|
||||
const svc = buildService(mocks);
|
||||
|
||||
const result = await svc.ingest(baseInput);
|
||||
|
||||
// Applied to main via the staging path, never the committing path.
|
||||
expect(mocks.gitService.stageSquashMergeIntoMain).toHaveBeenCalledWith('session/chat-1');
|
||||
expect(mocks.gitService.squashMergeIntoMain).not.toHaveBeenCalled();
|
||||
// No commit means no commit-message enhancement job.
|
||||
expect(mocks.configService.enqueueCommitMessageJobForExternalCommit).not.toHaveBeenCalled();
|
||||
// The session still applied successfully; there is just no commit hash.
|
||||
expect(result.commitHash).toBeNull();
|
||||
expect(mocks.sessionWorktreeService.cleanup).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ chatId: 'chat-1' }),
|
||||
'success',
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it('normalizes load_skill output to markdown while preserving structured payload', async () => {
|
||||
const tempDir = await mkdtemp(join(tmpdir(), 'ktx-memory-skill-'));
|
||||
const skillDir = join(tempDir, 'memory_agent');
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ describe('MemoryAgentService.reconcileCrossRefs', () => {
|
|||
knowledge: { userScopedKnowledgeEnabled: false },
|
||||
slValidation: { probeRowCount: 1 },
|
||||
llm: { memoryIngestionModel: 'test-model' },
|
||||
autoCommit: true,
|
||||
},
|
||||
promptService: undefined as never,
|
||||
skillsRegistry: undefined as never,
|
||||
|
|
@ -369,6 +370,7 @@ describe('MemoryAgentService.gateRevertInvalidSources (J3)', () => {
|
|||
knowledge: { userScopedKnowledgeEnabled: false },
|
||||
slValidation: { probeRowCount: 1 },
|
||||
llm: { memoryIngestionModel: 'test-model' },
|
||||
autoCommit: true,
|
||||
},
|
||||
promptService: undefined as never,
|
||||
skillsRegistry: undefined as never,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue