feat: rename project wiki directory

This commit is contained in:
Andrey Avtomonov 2026-05-13 15:32:05 +02:00
parent e1e9c4af91
commit 7ca96ce316
111 changed files with 423 additions and 423 deletions

View file

@ -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(

View file

@ -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);

View file

@ -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',
},

View file

@ -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);

View file

@ -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,
}),

View file

@ -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() {

View file

@ -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'],

View file

@ -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.