mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-13 11:22:11 +02:00
feat: rename project wiki directory
This commit is contained in:
parent
e1e9c4af91
commit
7ca96ce316
111 changed files with 423 additions and 423 deletions
|
|
@ -84,9 +84,9 @@ describe('KnowledgeWikiService.syncFromCommit', () => {
|
|||
const { service, pagesRepository, gitService } = makeService();
|
||||
|
||||
gitService.diffNameStatus.mockResolvedValue([
|
||||
{ status: 'A', path: 'knowledge/global/new-page.md' },
|
||||
{ status: 'M', path: 'knowledge/global/changed-page.md' },
|
||||
{ status: 'D', path: 'knowledge/global/gone-page.md' },
|
||||
{ status: 'A', path: 'wiki/global/new-page.md' },
|
||||
{ status: 'M', path: 'wiki/global/changed-page.md' },
|
||||
{ status: 'D', path: 'wiki/global/gone-page.md' },
|
||||
]);
|
||||
gitService.getFileAtCommit.mockImplementation((path: string) => {
|
||||
if (path.endsWith('new-page.md')) {
|
||||
|
|
@ -117,10 +117,10 @@ describe('KnowledgeWikiService.syncFromCommit', () => {
|
|||
const { service, pagesRepository, gitService, logger } = makeService();
|
||||
|
||||
gitService.diffNameStatus.mockResolvedValue([
|
||||
{ status: 'A', path: 'knowledge/global/revenue-policy.md' },
|
||||
{ status: 'A', path: 'knowledge/global/historic-sql-order-lifecycle.md' },
|
||||
{ status: 'A', path: 'knowledge/global/historic-sql/order-lifecycle.md' },
|
||||
{ status: 'A', path: 'knowledge/global/orbit/company-overview.md' },
|
||||
{ status: 'A', path: 'wiki/global/revenue-policy.md' },
|
||||
{ status: 'A', path: 'wiki/global/historic-sql-order-lifecycle.md' },
|
||||
{ status: 'A', path: 'wiki/global/historic-sql/order-lifecycle.md' },
|
||||
{ status: 'A', path: 'wiki/global/orbit/company-overview.md' },
|
||||
]);
|
||||
gitService.getFileAtCommit.mockImplementation((path: string) => {
|
||||
if (path.endsWith('revenue-policy.md')) {
|
||||
|
|
@ -137,13 +137,13 @@ describe('KnowledgeWikiService.syncFromCommit', () => {
|
|||
|
||||
await service.syncFromCommit('sha-before', 'sha-after', 'run-uuid');
|
||||
|
||||
expect(gitService.getFileAtCommit).not.toHaveBeenCalledWith('knowledge/global/orbit/company-overview.md', 'sha-after');
|
||||
expect(gitService.getFileAtCommit).not.toHaveBeenCalledWith('knowledge/global/historic-sql/order-lifecycle.md', 'sha-after');
|
||||
expect(gitService.getFileAtCommit).not.toHaveBeenCalledWith('wiki/global/orbit/company-overview.md', 'sha-after');
|
||||
expect(gitService.getFileAtCommit).not.toHaveBeenCalledWith('wiki/global/historic-sql/order-lifecycle.md', 'sha-after');
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'[knowledge.sync] skipping unparseable path: knowledge/global/orbit/company-overview.md',
|
||||
'[wiki.sync] skipping unparseable path: wiki/global/orbit/company-overview.md',
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'[knowledge.sync] skipping unparseable path: knowledge/global/historic-sql/order-lifecycle.md',
|
||||
'[wiki.sync] skipping unparseable path: wiki/global/historic-sql/order-lifecycle.md',
|
||||
);
|
||||
const call = pagesRepository.applyDiffTransactional.mock.calls[0][0];
|
||||
expect(call.upserts).toEqual(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { buildKnowledgeSearchText } from './knowledge-search-text.js';
|
|||
import type { KnowledgeGitDiffPort, KnowledgeIndexPort, UpsertPageParams } from './ports.js';
|
||||
import type { WikiFrontmatter, WikiPage, WikiPageWithScope } from './types.js';
|
||||
|
||||
const WIKI_PREFIX = 'knowledge';
|
||||
const WIKI_PREFIX = 'wiki';
|
||||
|
||||
export type { WikiFrontmatter };
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ export class KnowledgeWikiService {
|
|||
) {
|
||||
const path = this.pagePath(scope, scopeId, pageKey);
|
||||
const serialized = this.serializePage(frontmatter, content);
|
||||
const message = commitMessage ?? `Update knowledge page: ${pageKey}`;
|
||||
const message = commitMessage ?? `Update wiki page: ${pageKey}`;
|
||||
return this.configService.writeFile(path, serialized, author, authorEmail, message, {
|
||||
skipLock: options?.skipLock,
|
||||
});
|
||||
|
|
@ -115,7 +115,7 @@ export class KnowledgeWikiService {
|
|||
) {
|
||||
const path = this.pagePath(scope, scopeId, pageKey);
|
||||
try {
|
||||
return await this.configService.deleteFile(path, author, authorEmail, `Remove knowledge page: ${pageKey}`);
|
||||
return await this.configService.deleteFile(path, author, authorEmail, `Remove wiki page: ${pageKey}`);
|
||||
} catch (error) {
|
||||
// Check if the file actually exists — if not, deletion is a no-op
|
||||
try {
|
||||
|
|
@ -196,7 +196,7 @@ export class KnowledgeWikiService {
|
|||
rawContent,
|
||||
author,
|
||||
authorEmail,
|
||||
commitMessage ?? `Update knowledge page (raw): ${pageKey}`,
|
||||
commitMessage ?? `Update wiki page (raw): ${pageKey}`,
|
||||
);
|
||||
await this.syncSinglePage(scope, scopeId, pageKey, parsed.frontmatter, parsed.content);
|
||||
return parsed;
|
||||
|
|
@ -352,9 +352,9 @@ export class KnowledgeWikiService {
|
|||
|
||||
/**
|
||||
* Apply the diff between two commits on the config repo to the shared
|
||||
* `knowledge` index in a single transaction. Called by the ingest runner
|
||||
* wiki index in a single transaction. Called by the ingest runner
|
||||
* after Stage 6 squashes the session branch into main: the pre-squash main
|
||||
* SHA and the post-squash SHA bracket exactly the set of knowledge-file
|
||||
* SHA and the post-squash SHA bracket exactly the set of wiki-file
|
||||
* changes this run produced.
|
||||
*
|
||||
* Any added/modified file becomes an upsert (tagged with `source_run_id`),
|
||||
|
|
@ -362,7 +362,7 @@ export class KnowledgeWikiService {
|
|||
* transaction so the shared table stays consistent.
|
||||
*/
|
||||
async syncFromCommit(fromSha: string, toSha: string, runId: string): Promise<void> {
|
||||
const diff = await this.gitService.diffNameStatus(fromSha, toSha, 'knowledge/');
|
||||
const diff = await this.gitService.diffNameStatus(fromSha, toSha, 'wiki/');
|
||||
if (diff.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -372,7 +372,7 @@ export class KnowledgeWikiService {
|
|||
for (const entry of diff) {
|
||||
const parsedPath = parseKnowledgePath(entry.path);
|
||||
if (!parsedPath) {
|
||||
this.logger.warn(`[knowledge.sync] skipping unparseable path: ${entry.path}`);
|
||||
this.logger.warn(`[wiki.sync] skipping unparseable path: ${entry.path}`);
|
||||
continue;
|
||||
}
|
||||
if (entry.status === 'D') {
|
||||
|
|
@ -392,7 +392,7 @@ export class KnowledgeWikiService {
|
|||
embedding = await this.embeddingService.computeEmbedding(searchText);
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`[knowledge.sync] embedding failed for ${parsedPath.pageKey}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
`[wiki.sync] embedding failed for ${parsedPath.pageKey}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
}
|
||||
const contentHash = createHash('sha256').update(content).digest('hex');
|
||||
|
|
@ -410,21 +410,21 @@ export class KnowledgeWikiService {
|
|||
}
|
||||
|
||||
await this.pagesRepository.applyDiffTransactional({ runId, upserts, deletes });
|
||||
this.logger.log(`[knowledge.sync] run=${runId} applied ${upserts.length} upsert(s), ${deletes.length} delete(s)`);
|
||||
this.logger.log(`[wiki.sync] run=${runId} applied ${upserts.length} upsert(s), ${deletes.length} delete(s)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a `knowledge/<scope>/...` file path into its scope and page key.
|
||||
* `knowledge/global/foo.md` → { scope: 'GLOBAL', scopeId: null, pageKey: 'foo' }
|
||||
* `knowledge/user/<id>/bar.md` → { scope: 'USER', scopeId: '<id>', pageKey: 'bar' }
|
||||
* Parse a `wiki/<scope>/...` file path into its scope and page key.
|
||||
* `wiki/global/foo.md` → { scope: 'GLOBAL', scopeId: null, pageKey: 'foo' }
|
||||
* `wiki/user/<id>/bar.md` → { scope: 'USER', scopeId: '<id>', pageKey: 'bar' }
|
||||
*/
|
||||
function parseKnowledgePath(path: string): { scope: string; scopeId: string | null; pageKey: string } | null {
|
||||
if (!path.endsWith('.md')) {
|
||||
return null;
|
||||
}
|
||||
const segments = path.split('/');
|
||||
if (segments[0] !== 'knowledge') {
|
||||
if (segments[0] !== 'wiki') {
|
||||
return null;
|
||||
}
|
||||
const rest = segments.slice(1);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ describe('local knowledge helpers', () => {
|
|||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('writes, reads, lists, and searches global knowledge pages', async () => {
|
||||
it('writes, reads, lists, and searches global wiki pages', async () => {
|
||||
const write = await writeLocalKnowledgePage(project, {
|
||||
key: 'metrics-revenue',
|
||||
scope: 'GLOBAL',
|
||||
|
|
@ -46,7 +46,7 @@ describe('local knowledge helpers', () => {
|
|||
slRefs: ['orders'],
|
||||
});
|
||||
|
||||
expect(write.path).toBe('knowledge/global/metrics-revenue.md');
|
||||
expect(write.path).toBe('wiki/global/metrics-revenue.md');
|
||||
expect(write.operation).toBe('write');
|
||||
|
||||
await expect(readLocalKnowledgePage(project, { key: 'metrics-revenue', userId: 'local' })).resolves.toMatchObject({
|
||||
|
|
@ -62,7 +62,7 @@ describe('local knowledge helpers', () => {
|
|||
await expect(listLocalKnowledgePages(project, { userId: 'local' })).resolves.toEqual([
|
||||
{
|
||||
key: 'metrics-revenue',
|
||||
path: 'knowledge/global/metrics-revenue.md',
|
||||
path: 'wiki/global/metrics-revenue.md',
|
||||
scope: 'GLOBAL',
|
||||
summary: 'Revenue metric definition',
|
||||
},
|
||||
|
|
@ -72,7 +72,7 @@ describe('local knowledge helpers', () => {
|
|||
expect(search).toEqual([
|
||||
expect.objectContaining({
|
||||
key: 'metrics-revenue',
|
||||
path: 'knowledge/global/metrics-revenue.md',
|
||||
path: 'wiki/global/metrics-revenue.md',
|
||||
scope: 'GLOBAL',
|
||||
score: expect.any(Number),
|
||||
matchReasons: expect.arrayContaining(['lexical']),
|
||||
|
|
@ -195,7 +195,7 @@ describe('local knowledge helpers', () => {
|
|||
fingerprints: ['fp_paid_orders'],
|
||||
});
|
||||
|
||||
const raw = await project.fileStore.readFile('knowledge/global/monthly-paid-orders.md');
|
||||
const raw = await project.fileStore.readFile('wiki/global/monthly-paid-orders.md');
|
||||
expect(raw.content).toContain('source: historic-sql');
|
||||
expect(raw.content).toContain('intent: Monthly paid order count');
|
||||
expect(raw.content).toContain(['tables:', ' - analytics.orders'].join('\n'));
|
||||
|
|
@ -245,7 +245,7 @@ describe('local knowledge helpers', () => {
|
|||
).rejects.toThrow('Invalid wiki key "orbit/company-overview". Wiki keys must be flat; use "orbit-company-overview".');
|
||||
});
|
||||
|
||||
it('ignores nested historic-SQL legacy paths when listing local knowledge pages', async () => {
|
||||
it('ignores nested historic-SQL legacy paths when listing local wiki pages', async () => {
|
||||
await writeLocalKnowledgePage(project, {
|
||||
key: 'historic-sql-paid-orders',
|
||||
scope: 'GLOBAL',
|
||||
|
|
@ -254,7 +254,7 @@ describe('local knowledge helpers', () => {
|
|||
tags: ['historic-sql'],
|
||||
});
|
||||
await project.fileStore.writeFile(
|
||||
'knowledge/global/historic-sql/paid-orders.md',
|
||||
'wiki/global/historic-sql/paid-orders.md',
|
||||
'---\nsummary: Nested historic SQL page\nusage_mode: auto\n---\n\nNested body\n',
|
||||
'Test',
|
||||
'test@example.com',
|
||||
|
|
@ -264,7 +264,7 @@ describe('local knowledge helpers', () => {
|
|||
await expect(listLocalKnowledgePages(project, { userId: 'local' })).resolves.toEqual([
|
||||
{
|
||||
key: 'historic-sql-paid-orders',
|
||||
path: 'knowledge/global/historic-sql-paid-orders.md',
|
||||
path: 'wiki/global/historic-sql-paid-orders.md',
|
||||
scope: 'GLOBAL',
|
||||
summary: 'Flat historic SQL page',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -75,13 +75,13 @@ function stringArray(value: unknown): string[] {
|
|||
function knowledgePath(scope: LocalKnowledgeScope, userId: string | undefined, key: string): string {
|
||||
const safeKey = assertFlatWikiKey(key);
|
||||
if (scope === 'GLOBAL') {
|
||||
return `knowledge/global/${safeKey}.md`;
|
||||
return `wiki/global/${safeKey}.md`;
|
||||
}
|
||||
return `knowledge/user/${assertSafePathToken('user id', userId ?? 'local')}/${safeKey}.md`;
|
||||
return `wiki/user/${assertSafePathToken('user id', userId ?? 'local')}/${safeKey}.md`;
|
||||
}
|
||||
|
||||
function keyFromKnowledgePath(path: string, scope: LocalKnowledgeScope, userId: string): string | null {
|
||||
const prefix = scope === 'GLOBAL' ? 'knowledge/global/' : `knowledge/user/${assertSafePathToken('user id', userId)}/`;
|
||||
const prefix = scope === 'GLOBAL' ? 'wiki/global/' : `wiki/user/${assertSafePathToken('user id', userId)}/`;
|
||||
const key = path.slice(prefix.length).replace(/\.md$/, '');
|
||||
if (isFlatWikiKey(key)) {
|
||||
return key;
|
||||
|
|
@ -158,7 +158,7 @@ export async function writeLocalKnowledgePage(
|
|||
serializeKnowledgePage(input),
|
||||
LOCAL_AUTHOR,
|
||||
LOCAL_AUTHOR_EMAIL,
|
||||
`Write knowledge page: ${input.key}`,
|
||||
`Write wiki page: ${input.key}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ export async function listLocalKnowledgePages(
|
|||
const userId = input.userId ?? 'local';
|
||||
const pages: LocalKnowledgeSummary[] = [];
|
||||
for (const scope of ['GLOBAL', 'USER'] as const) {
|
||||
const root = scope === 'GLOBAL' ? 'knowledge/global' : `knowledge/user/${assertSafePathToken('user id', userId)}`;
|
||||
const root = scope === 'GLOBAL' ? 'wiki/global' : `wiki/user/${assertSafePathToken('user id', userId)}`;
|
||||
const listed = await project.fileStore.listFiles(root);
|
||||
for (const path of listed.files.filter((file) => file.endsWith('.md')).sort()) {
|
||||
const key = keyFromKnowledgePath(path, scope, userId);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe('SqliteKnowledgeIndex', () => {
|
|||
|
||||
function page(overrides: Partial<SqliteKnowledgeIndexPage> = {}): SqliteKnowledgeIndexPage {
|
||||
return {
|
||||
path: 'knowledge/global/revenue.md',
|
||||
path: 'wiki/global/revenue.md',
|
||||
key: 'revenue',
|
||||
scope: 'GLOBAL',
|
||||
summary: 'Revenue definition',
|
||||
|
|
@ -36,7 +36,7 @@ describe('SqliteKnowledgeIndex', () => {
|
|||
index.sync([
|
||||
page(),
|
||||
page({
|
||||
path: 'knowledge/global/support.md',
|
||||
path: 'wiki/global/support.md',
|
||||
key: 'support',
|
||||
summary: 'Support queue',
|
||||
content: 'Tickets are grouped by priority.',
|
||||
|
|
@ -47,8 +47,8 @@ describe('SqliteKnowledgeIndex', () => {
|
|||
await expect(access(dbPath)).resolves.toBeUndefined();
|
||||
expect(index.searchLexicalCandidates({ queryText: 'paid order', limit: 10 })).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'knowledge/global/revenue.md',
|
||||
path: 'knowledge/global/revenue.md',
|
||||
id: 'wiki/global/revenue.md',
|
||||
path: 'wiki/global/revenue.md',
|
||||
rank: 1,
|
||||
rawScore: expect.any(Number),
|
||||
}),
|
||||
|
|
@ -57,7 +57,7 @@ describe('SqliteKnowledgeIndex', () => {
|
|||
|
||||
it('removes stale rows when the Markdown source list changes', () => {
|
||||
const index = new SqliteKnowledgeIndex({ dbPath });
|
||||
index.rebuild([page(), page({ path: 'knowledge/global/churn.md', key: 'churn', content: 'Churn risk.' })]);
|
||||
index.rebuild([page(), page({ path: 'wiki/global/churn.md', key: 'churn', content: 'Churn risk.' })]);
|
||||
expect(index.search('churn', 10)).toHaveLength(1);
|
||||
|
||||
index.rebuild([page()]);
|
||||
|
|
@ -67,12 +67,12 @@ describe('SqliteKnowledgeIndex', () => {
|
|||
|
||||
it('exposes existing search text and embedding state for incremental refresh', () => {
|
||||
const index = new SqliteKnowledgeIndex({ dbPath });
|
||||
index.sync([page({ path: 'knowledge/global/revenue.md', key: 'revenue', embedding: [1, 0] })]);
|
||||
index.sync([page({ path: 'wiki/global/revenue.md', key: 'revenue', embedding: [1, 0] })]);
|
||||
|
||||
expect(index.getExistingPages()).toEqual(
|
||||
new Map([
|
||||
[
|
||||
'knowledge/global/revenue.md',
|
||||
'wiki/global/revenue.md',
|
||||
expect.objectContaining({
|
||||
searchText: expect.stringContaining('Revenue definition'),
|
||||
embedding: [1, 0],
|
||||
|
|
@ -84,29 +84,29 @@ describe('SqliteKnowledgeIndex', () => {
|
|||
|
||||
it('does not treat empty embeddings as indexed semantic vectors', () => {
|
||||
const index = new SqliteKnowledgeIndex({ dbPath });
|
||||
index.sync([page({ path: 'knowledge/global/revenue.md', key: 'revenue', embedding: [] })]);
|
||||
index.sync([page({ path: 'wiki/global/revenue.md', key: 'revenue', embedding: [] })]);
|
||||
|
||||
expect(index.getExistingPages().get('knowledge/global/revenue.md')?.embedding).toBeNull();
|
||||
expect(index.getExistingPages().get('wiki/global/revenue.md')?.embedding).toBeNull();
|
||||
expect(index.searchSemanticCandidates({ queryEmbedding: [1, 0], limit: 10 })).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns semantic lane candidates from stored page embeddings', () => {
|
||||
const index = new SqliteKnowledgeIndex({ dbPath });
|
||||
index.sync([
|
||||
page({ path: 'knowledge/global/revenue.md', key: 'revenue', embedding: [1, 0] }),
|
||||
page({ path: 'knowledge/global/support.md', key: 'support', summary: 'Support queue', embedding: [0, 1] }),
|
||||
page({ path: 'wiki/global/revenue.md', key: 'revenue', embedding: [1, 0] }),
|
||||
page({ path: 'wiki/global/support.md', key: 'support', summary: 'Support queue', embedding: [0, 1] }),
|
||||
]);
|
||||
|
||||
expect(index.searchSemanticCandidates({ queryEmbedding: [1, 0], limit: 10 })).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'knowledge/global/revenue.md',
|
||||
path: 'knowledge/global/revenue.md',
|
||||
id: 'wiki/global/revenue.md',
|
||||
path: 'wiki/global/revenue.md',
|
||||
rank: 1,
|
||||
rawScore: 1,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'knowledge/global/support.md',
|
||||
path: 'knowledge/global/support.md',
|
||||
id: 'wiki/global/support.md',
|
||||
path: 'wiki/global/support.md',
|
||||
rank: 2,
|
||||
rawScore: 0,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export class WikiRemoveTool extends BaseTool<typeof wikiRemoveInputSchema> {
|
|||
}
|
||||
|
||||
get description(): string {
|
||||
return `<purpose>Remove a knowledge page that is no longer relevant.</purpose>`;
|
||||
return `<purpose>Remove a wiki page that is no longer relevant.</purpose>`;
|
||||
}
|
||||
|
||||
get inputSchema() {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ describe('WikiSearchTool', () => {
|
|||
results: [
|
||||
{
|
||||
key: 'metrics-revenue',
|
||||
path: 'knowledge/global/metrics-revenue.md',
|
||||
path: 'wiki/global/metrics-revenue.md',
|
||||
scope: 'GLOBAL' as const,
|
||||
summary: 'Revenue metric definition',
|
||||
score: 0.02459016393442623,
|
||||
|
|
@ -28,7 +28,7 @@ describe('WikiSearchTool', () => {
|
|||
results: [
|
||||
{
|
||||
blockKey: 'metrics-revenue',
|
||||
path: 'knowledge/global/metrics-revenue.md',
|
||||
path: 'wiki/global/metrics-revenue.md',
|
||||
summary: 'Revenue metric definition',
|
||||
score: 0.02459016393442623,
|
||||
matchReasons: ['lexical', 'token'],
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export class WikiWriteTool extends BaseTool<typeof wikiWriteInputSchema> {
|
|||
|
||||
get description(): string {
|
||||
return `<purpose>
|
||||
Create or update a knowledge page. Provide content for create/rewrite, or replacements for targeted edits.
|
||||
Create or update a wiki page. Provide content for create/rewrite, or replacements for targeted edits.
|
||||
For existing pages, you may provide only frontmatter fields such as summary, tags, refs, or sl_refs to update metadata while preserving content.
|
||||
tags/refs/sl_refs use REPLACE semantics: omit to keep existing on update, [] to clear, [values] to set.
|
||||
Keys must be flat file names, not directory paths. Use tags/source frontmatter for grouping.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue