Initial open-source release

This commit is contained in:
Andrey Avtomonov 2026-05-10 23:12:26 +02:00
commit 1a42152e6f
1199 changed files with 257054 additions and 0 deletions

View file

@ -0,0 +1,5 @@
export { WikiListTagsTool } from './wiki-list-tags.tool.js';
export { WikiReadTool } from './wiki-read.tool.js';
export { WikiRemoveTool } from './wiki-remove.tool.js';
export { WikiSearchTool } from './wiki-search.tool.js';
export { WikiWriteTool } from './wiki-write.tool.js';

View file

@ -0,0 +1,42 @@
import { describe, expect, it, vi } from 'vitest';
import type { ToolContext } from '../../tools/index.js';
import { WikiListTagsTool } from './wiki-list-tags.tool.js';
describe('WikiListTagsTool', () => {
const baseContext: ToolContext = { sourceId: 's', messageId: 'm', userId: 'u' };
it("returns distinct sorted tags across the user's visible pages", async () => {
const pagesRepository = {
listPagesForUser: vi.fn().mockResolvedValue([
{ scope: 'GLOBAL', scope_id: null, page_key: 'k1' },
{ scope: 'USER', scope_id: 'u', page_key: 'k2' },
]),
};
const wikiService = {
readPage: vi.fn().mockImplementation((_scope, _scopeId, key) => {
if (key === 'k1') {
return Promise.resolve({ frontmatter: { tags: ['metrics', 'finance'] }, content: '' });
}
if (key === 'k2') {
return Promise.resolve({ frontmatter: { tags: ['metrics'] }, content: '' });
}
return Promise.resolve(null);
}),
};
const tool = new WikiListTagsTool(wikiService as any, pagesRepository as any);
const result = await tool.call({}, baseContext);
expect(result.markdown).toContain('finance');
expect(result.markdown).toContain('metrics');
expect(result.structured.tags).toEqual(['finance', 'metrics']);
});
it('returns a friendly message when no pages have tags', async () => {
const pagesRepository = { listPagesForUser: vi.fn().mockResolvedValue([]) };
const wikiService = { readPage: vi.fn() };
const tool = new WikiListTagsTool(wikiService as any, pagesRepository as any);
const result = await tool.call({}, baseContext);
expect(result.markdown).toMatch(/no tags/i);
});
});

View file

@ -0,0 +1,49 @@
import { z } from 'zod';
import type { KnowledgeIndexPort } from '../ports.js';
type BlockScope = 'GLOBAL' | 'USER';
import { KnowledgeWikiService } from '../index.js';
import { BaseTool, type ToolContext, type ToolOutput } from '../../tools/index.js';
const wikiListTagsInputSchema = z.object({});
type WikiListTagsInput = z.infer<typeof wikiListTagsInputSchema>;
export class WikiListTagsTool extends BaseTool<typeof wikiListTagsInputSchema> {
readonly name = 'wiki_list_tags';
constructor(
private readonly wikiService: KnowledgeWikiService,
private readonly pagesRepository: KnowledgeIndexPort,
) {
super();
}
get description(): string {
return `<purpose>
List distinct topic tags across all wiki pages visible to the user.
Call before writing a new page so you can reuse existing tags consistently instead of coining near-duplicates.
</purpose>`;
}
get inputSchema() {
return wikiListTagsInputSchema;
}
async call(_input: WikiListTagsInput, context: ToolContext): Promise<ToolOutput<{ tags: string[] }>> {
const pages = await this.pagesRepository.listPagesForUser(context.userId);
const set = new Set<string>();
for (const p of pages) {
const scope = p.scope as BlockScope;
const scopeId = scope === 'USER' ? p.scope_id : null;
const page = await this.wikiService.readPage(scope, scopeId, p.page_key);
for (const t of page?.frontmatter.tags ?? []) {
set.add(t);
}
}
const tags = [...set].sort();
return {
markdown: tags.length === 0 ? '(no tags in use yet)' : tags.join(', '),
structured: { tags },
};
}
}

View file

@ -0,0 +1,82 @@
import { z } from 'zod';
import type { KnowledgeIndexPort } from '../ports.js';
import { KnowledgeWikiService } from '../index.js';
import { BaseTool, type ToolContext, type ToolOutput } from '../../tools/index.js';
const WikiReadInputSchema = z.object({
key: z
.string()
.describe('The block_key to read. Check the <knowledge_index> in the system prompt for available keys.'),
});
type WikiReadInput = z.infer<typeof WikiReadInputSchema>;
interface WikiReadStructured {
blockKey: string;
content: string;
scope: string;
found: boolean;
tags?: string[];
refs?: string[];
}
export class WikiReadTool extends BaseTool<typeof WikiReadInputSchema> {
readonly name = 'wiki_read';
constructor(
private readonly wikiService: KnowledgeWikiService,
private readonly pagesRepository: KnowledgeIndexPort,
) {
super();
}
get description(): string {
return (
'Load the full content of a knowledge block by its key. ' +
'Use this to retrieve detailed rules, preferences, or definitions listed in the <knowledge_index>. ' +
'Call this when the user query relates to a topic covered by an available knowledge block.'
);
}
get inputSchema() {
return WikiReadInputSchema;
}
async call(input: WikiReadInput, context: ToolContext): Promise<ToolOutput<WikiReadStructured>> {
const page = await this.wikiService.readPageForUser(context.userId, input.key);
if (!page) {
return {
markdown: `No knowledge block found with key "${input.key}".`,
structured: { blockKey: input.key, content: '', scope: '', found: false },
};
}
const indexEntry = await this.pagesRepository.findPageByKey(
page.scope,
page.scope === 'USER' ? context.userId : null,
input.key,
);
if (indexEntry?.id) {
void this.pagesRepository.incrementUsageCount([indexEntry.id]);
}
let md = `## ${page.pageKey}\n\n${page.content}`;
const refs = page.frontmatter.refs;
if (refs && refs.length > 0) {
md += `\n\nSee also: ${refs.map((r) => `[[${r}]]`).join(', ')}`;
}
return {
markdown: md,
structured: {
blockKey: page.pageKey,
content: page.content,
scope: page.scope,
found: true,
tags: page.frontmatter.tags,
refs: page.frontmatter.refs,
},
};
}
}

View file

@ -0,0 +1,59 @@
import { describe, expect, it, vi } from 'vitest';
import type { ToolSession } from '../../tools/index.js';
import { createTouchedSlSources, type ToolContext } from '../../tools/index.js';
import { WikiRemoveTool } from './wiki-remove.tool.js';
describe('WikiRemoveTool', () => {
const baseContext: ToolContext = { sourceId: 's', messageId: 'm', userId: 'u' };
it('removes an existing page when no session is present', async () => {
const wikiService = {
deletePage: vi.fn().mockResolvedValue(undefined),
deleteFromIndex: vi.fn().mockResolvedValue(undefined),
};
const pagesRepository = {
findPageByKey: vi.fn().mockResolvedValue({ page_key: 'old' }),
};
const knowledgeRepository = { createEvent: vi.fn().mockResolvedValue(undefined) };
const tool = new WikiRemoveTool(wikiService as any, pagesRepository as any, knowledgeRepository as any);
const result = await tool.call({ key: 'old' } as any, baseContext);
expect(wikiService.deletePage).toHaveBeenCalledTimes(1);
expect(wikiService.deleteFromIndex).toHaveBeenCalledTimes(1);
expect(result.markdown).toMatch(/removed/i);
});
it('skips deleteFromIndex when session is worktree-scoped', async () => {
const wikiService = {
deletePage: vi.fn().mockResolvedValue(undefined),
deleteFromIndex: vi.fn().mockResolvedValue(undefined),
};
const pagesRepository = { findPageByKey: vi.fn().mockResolvedValue({ page_key: 'old' }) };
const knowledgeRepository = { createEvent: vi.fn().mockResolvedValue(undefined) };
const tool = new WikiRemoveTool(wikiService as any, pagesRepository as any, knowledgeRepository as any);
const session: ToolSession = {
connectionId: 'c',
isWorktreeScoped: true,
preHead: null,
touchedSlSources: createTouchedSlSources(),
actions: [],
semanticLayerService: {} as any,
wikiService: wikiService as any,
configService: {} as any,
gitService: {} as any,
};
await tool.call({ key: 'old' } as any, { ...baseContext, session });
expect(wikiService.deletePage).toHaveBeenCalledTimes(1);
expect(wikiService.deleteFromIndex).not.toHaveBeenCalled();
expect(session.actions).toContainEqual(expect.objectContaining({ target: 'wiki', type: 'removed', key: 'old' }));
});
it('returns a friendly message when the page does not exist', async () => {
const wikiService = { deletePage: vi.fn(), deleteFromIndex: vi.fn() };
const pagesRepository = { findPageByKey: vi.fn().mockResolvedValue(null) };
const knowledgeRepository = { createEvent: vi.fn() };
const tool = new WikiRemoveTool(wikiService as any, pagesRepository as any, knowledgeRepository as any);
const result = await tool.call({ key: 'missing' } as any, baseContext);
expect(result.structured.success).toBe(false);
expect(result.markdown).toMatch(/not found/i);
});
});

View file

@ -0,0 +1,85 @@
import { z } from 'zod';
import type { KnowledgeIndexPort } from '../ports.js';
import type { KnowledgeEventPort } from '../ports.js';
type BlockScope = 'GLOBAL' | 'USER';
import { KnowledgeWikiService } from '../index.js';
import { BaseTool, type ToolContext, type ToolOutput } from '../../tools/index.js';
const SYSTEM_AUTHOR = 'System User';
const SYSTEM_EMAIL = 'system@example.com';
const wikiRemoveInputSchema = z.object({
key: z.string().describe('The page key to remove'),
});
type WikiRemoveInput = z.infer<typeof wikiRemoveInputSchema>;
interface WikiRemoveStructured {
success: boolean;
key: string;
}
export class WikiRemoveTool extends BaseTool<typeof wikiRemoveInputSchema> {
readonly name = 'wiki_remove';
constructor(
private readonly wikiService: KnowledgeWikiService,
private readonly pagesRepository: KnowledgeIndexPort,
private readonly knowledgeRepository: KnowledgeEventPort,
) {
super();
}
get description(): string {
return `<purpose>Remove a knowledge page that is no longer relevant.</purpose>`;
}
get inputSchema() {
return wikiRemoveInputSchema;
}
async call(input: WikiRemoveInput, context: ToolContext): Promise<ToolOutput<WikiRemoveStructured>> {
const wikiService = context.session?.wikiService ?? this.wikiService;
const writesGlobal = !!context.session;
const skipIndex = context.session?.isWorktreeScoped === true;
const scope: BlockScope = writesGlobal ? 'GLOBAL' : 'USER';
const scopeId = scope === 'USER' ? context.userId : null;
const existing = await this.pagesRepository.findPageByKey(scope, scopeId, input.key);
if (!existing) {
return {
markdown: `Page "${input.key}" not found.`,
structured: { success: false, key: input.key },
};
}
await wikiService.deletePage(scope, scopeId, input.key, SYSTEM_AUTHOR, SYSTEM_EMAIL);
if (!skipIndex) {
await wikiService.deleteFromIndex(scope, scopeId, input.key);
}
await this.knowledgeRepository.createEvent({
blockId: null,
eventType: 'BLOCK_REMOVED',
actorId: context.userId,
chatId: null,
messageId: null,
payload: { removedKey: input.key, blockKey: input.key },
});
if (context.session) {
context.session.actions.push({
target: 'wiki',
type: 'removed',
key: input.key,
detail: `Removed page "${input.key}"`,
});
}
return {
markdown: `Page "${input.key}" removed.`,
structured: { success: true, key: input.key },
};
}
}

View file

@ -0,0 +1,41 @@
import { describe, expect, it, vi } from 'vitest';
import { WikiSearchTool } from './wiki-search.tool.js';
describe('WikiSearchTool', () => {
it('searches through the injected wiki adapter port', async () => {
const search = vi.fn(async () => ({
results: [
{
key: 'metrics/revenue',
path: 'knowledge/global/metrics/revenue.md',
scope: 'GLOBAL' as const,
summary: 'Revenue metric definition',
score: 0.02459016393442623,
matchReasons: ['lexical' as const, 'token' as const],
},
],
totalFound: 1,
}));
const tool = new WikiSearchTool({ search });
const result = await tool.call(
{ query: 'paid order', limit: 5 },
{ sourceId: 'test', messageId: 'message-1', userId: 'agent' },
);
expect(search).toHaveBeenCalledWith({ userId: 'agent', query: 'paid order', limit: 5 });
expect(result.structured).toEqual({
results: [
{
blockKey: 'metrics/revenue',
path: 'knowledge/global/metrics/revenue.md',
summary: 'Revenue metric definition',
score: 0.02459016393442623,
matchReasons: ['lexical', 'token'],
},
],
totalFound: 1,
});
expect(result.markdown).toContain('**metrics/revenue**');
});
});

View file

@ -0,0 +1,92 @@
import { z } from 'zod';
import { BaseTool, type ToolContext, type ToolOutput } from '../../tools/index.js';
import type { WikiSearchLaneSummary, WikiSearchMatchReason } from '../types.js';
const WikiSearchInputSchema = z.object({
query: z.string().describe('Natural language search query to find relevant knowledge blocks.'),
limit: z.number().optional().default(10).describe('Maximum number of results to return (default 10).'),
});
type WikiSearchInput = z.infer<typeof WikiSearchInputSchema>;
interface WikiSearchResult {
blockKey: string;
path: string;
summary: string;
score: number;
matchReasons?: WikiSearchMatchReason[];
lanes?: WikiSearchLaneSummary[];
}
interface WikiSearchStructured {
results: WikiSearchResult[];
totalFound: number;
}
export interface WikiSearchAdapterPort {
search(input: { userId: string; query: string; limit: number }): Promise<{
results: Array<{
key: string;
path: string;
summary: string;
score: number;
matchReasons?: WikiSearchMatchReason[];
lanes?: WikiSearchLaneSummary[];
}>;
totalFound: number;
}>;
}
export class WikiSearchTool extends BaseTool<typeof WikiSearchInputSchema> {
readonly name = 'wiki_search';
constructor(private readonly searchAdapter: WikiSearchAdapterPort) {
super();
}
get description(): string {
return (
'Search knowledge blocks by hybrid lexical, semantic, and token matching. ' +
'Use this when you need to find knowledge on a topic not visible in the discovery index. ' +
'Returns ranked summaries — use wiki_read to load the full content of specific results.'
);
}
get inputSchema() {
return WikiSearchInputSchema;
}
async call(input: WikiSearchInput, context: ToolContext): Promise<ToolOutput<WikiSearchStructured>> {
const response = await this.searchAdapter.search({
userId: context.userId,
query: input.query,
limit: input.limit,
});
if (response.results.length === 0) {
return {
markdown: `No knowledge blocks found matching "${input.query}".`,
structured: { results: [], totalFound: 0 },
};
}
const lines = response.results.map((r, i) => `${i + 1}. **${r.key}**: ${r.summary}`);
const structured: WikiSearchStructured = {
results: response.results.map((r) => ({
blockKey: r.key,
path: r.path,
summary: r.summary,
score: r.score,
matchReasons: r.matchReasons,
lanes: r.lanes,
})),
totalFound: response.totalFound,
};
return {
markdown: `Found ${response.results.length} knowledge block(s):\n\n${lines.join('\n')}`,
structured,
};
}
}

View file

@ -0,0 +1,168 @@
import { describe, expect, it, vi } from 'vitest';
import type { ToolSession } from '../../tools/index.js';
import { createTouchedSlSources, type ToolContext } from '../../tools/index.js';
import { WikiWriteTool } from './wiki-write.tool.js';
function makeTool(overrides: any = {}) {
const wikiService = {
readPage: vi.fn().mockResolvedValue(null),
writePage: vi.fn().mockResolvedValue(undefined),
syncSinglePage: vi.fn().mockResolvedValue(undefined),
...overrides.wikiService,
};
const pagesRepository = {
findPageByKey: vi.fn().mockResolvedValue(null),
getUserPageCount: vi.fn().mockResolvedValue(0),
...overrides.pagesRepository,
};
const knowledgeRepository = {
createEvent: vi.fn().mockResolvedValue(undefined),
...overrides.knowledgeRepository,
};
const tool = new WikiWriteTool(wikiService as any, pagesRepository as any, knowledgeRepository as any);
return { tool, wikiService, pagesRepository, knowledgeRepository };
}
describe('WikiWriteTool', () => {
const baseContext: ToolContext = { sourceId: 's', messageId: 'm', userId: 'u' };
it('creates a new page and indexes it when no session is present', async () => {
const { tool, wikiService } = makeTool();
const result = await tool.call(
{ key: 'leads-source', summary: 'Lead source definitions', content: '# Leads' } as any,
baseContext,
);
expect(wikiService.writePage).toHaveBeenCalledTimes(1);
expect(wikiService.syncSinglePage).toHaveBeenCalledTimes(1);
expect(result.markdown).toMatch(/created/i);
});
it('skips syncSinglePage when session is worktree-scoped', async () => {
const { tool, wikiService } = makeTool();
const session: ToolSession = {
connectionId: 'conn-1',
isWorktreeScoped: true,
preHead: null,
touchedSlSources: createTouchedSlSources(),
actions: [],
semanticLayerService: {} as any,
wikiService: wikiService as any,
configService: {} as any,
gitService: {} as any,
};
const context: ToolContext = { ...baseContext, session };
await tool.call({ key: 'k', summary: 's', content: '# x' } as any, context);
expect(wikiService.writePage).toHaveBeenCalledTimes(1);
expect(wikiService.syncSinglePage).not.toHaveBeenCalled();
expect(session.actions).toContainEqual(expect.objectContaining({ target: 'wiki', type: 'created', key: 'k' }));
});
it('requires either content or replacements', async () => {
const { tool } = makeTool();
const result = await tool.call({ key: 'k', summary: 's' } as any, baseContext);
expect(result.structured.success).toBe(false);
expect(result.markdown).toMatch(/content.*or.*replacements/i);
});
it('writes historic-SQL frontmatter fields', async () => {
const { tool, wikiService } = makeTool();
await tool.call(
{
key: 'queries/monthly-paid-orders',
summary: 'Monthly paid orders',
tags: ['historic-sql', 'query-pattern'],
sl_refs: ['analytics.orders'],
source: 'historic-sql',
intent: 'Monthly paid order count',
tables: ['analytics.orders'],
representative_sql: "SELECT count(*) FROM analytics.orders WHERE status = 'paid'",
usage: {
executions: 42,
distinct_users: 3,
first_seen: '2026-02-01',
last_seen: '2026-05-04',
p50_runtime_ms: 100,
p95_runtime_ms: 200,
error_rate: 0,
rows_produced: 42,
},
fingerprints: ['fp_paid_orders'],
content: '## Monthly paid order count',
} as any,
baseContext,
);
expect(wikiService.writePage.mock.calls[0][3]).toEqual({
summary: 'Monthly paid orders',
usage_mode: 'auto',
sort_order: 0,
tags: ['historic-sql', 'query-pattern'],
refs: undefined,
sl_refs: ['analytics.orders'],
source: 'historic-sql',
intent: 'Monthly paid order count',
tables: ['analytics.orders'],
representative_sql: "SELECT count(*) FROM analytics.orders WHERE status = 'paid'",
usage: {
executions: 42,
distinct_users: 3,
first_seen: '2026-02-01',
last_seen: '2026-05-04',
p50_runtime_ms: 100,
p95_runtime_ms: 200,
error_rate: 0,
rows_produced: 42,
},
fingerprints: ['fp_paid_orders'],
});
});
it('preserves historic-SQL frontmatter fields when update omits them', async () => {
const existingFrontmatter = {
summary: 'Monthly paid orders',
usage_mode: 'auto' as const,
sort_order: 0,
tags: ['historic-sql'],
sl_refs: ['analytics.orders'],
source: 'historic-sql',
intent: 'Monthly paid order count',
tables: ['analytics.orders'],
representative_sql: "SELECT count(*) FROM analytics.orders WHERE status = 'paid'",
usage: {
executions: 42,
distinct_users: 3,
first_seen: '2026-02-01',
last_seen: '2026-05-04',
p50_runtime_ms: 100,
p95_runtime_ms: 200,
error_rate: 0,
rows_produced: 42,
},
fingerprints: ['fp_paid_orders'],
};
const { tool, wikiService } = makeTool({
wikiService: {
readPage: vi.fn().mockResolvedValue({
pageKey: 'queries/monthly-paid-orders',
frontmatter: existingFrontmatter,
content: 'old body',
}),
},
});
await tool.call(
{
key: 'queries/monthly-paid-orders',
summary: 'Monthly paid orders updated',
content: '## Monthly paid order count updated',
} as any,
baseContext,
);
expect(wikiService.writePage.mock.calls[0][3]).toEqual({
...existingFrontmatter,
summary: 'Monthly paid orders updated',
});
});
});

View file

@ -0,0 +1,167 @@
import { z } from 'zod';
import type { KnowledgeIndexPort } from '../ports.js';
import type { KnowledgeEventPort } from '../ports.js';
type BlockScope = 'GLOBAL' | 'USER';
import { KnowledgeWikiService, type WikiFrontmatter } from '../index.js';
import { applySqlEdits } from '../../tools/sql-edit-replacer.js';
import { BaseTool, type ToolContext, type ToolOutput } from '../../tools/index.js';
const MAX_USER_BLOCKS = 100;
const SYSTEM_AUTHOR = 'System User';
const SYSTEM_EMAIL = 'system@example.com';
const historicSqlUsageFrontmatterSchema = z.object({
executions: z.number().int().nonnegative(),
distinct_users: z.number().int().nonnegative(),
first_seen: z.string().min(1),
last_seen: z.string().min(1),
p50_runtime_ms: z.number().nonnegative().nullable(),
p95_runtime_ms: z.number().nonnegative().nullable(),
error_rate: z.number().min(0).max(1),
rows_produced: z.number().int().nonnegative().optional(),
});
const wikiWriteInputSchema = z.object({
key: z.string().max(120),
summary: z.string().max(200),
content: z.string().max(4000).optional(),
replacements: z
.array(z.object({ oldText: z.string(), newText: z.string(), reason: z.string().optional() }))
.optional(),
tags: z.array(z.string()).optional(),
refs: z.array(z.string()).optional(),
sl_refs: z.array(z.string()).optional(),
source: z.string().optional(),
intent: z.string().optional(),
tables: z.array(z.string()).optional(),
representative_sql: z.string().optional(),
usage: historicSqlUsageFrontmatterSchema.optional(),
fingerprints: z.array(z.string()).optional(),
});
type WikiWriteInput = z.infer<typeof wikiWriteInputSchema>;
interface WikiWriteStructured {
success: boolean;
key: string;
action?: 'created' | 'updated';
}
export class WikiWriteTool extends BaseTool<typeof wikiWriteInputSchema> {
readonly name = 'wiki_write';
constructor(
private readonly wikiService: KnowledgeWikiService,
private readonly pagesRepository: KnowledgeIndexPort,
private readonly knowledgeRepository: KnowledgeEventPort,
) {
super();
}
get description(): string {
return `<purpose>
Create or update a knowledge page. Provide content for create/rewrite, or replacements for targeted edits.
tags/refs/sl_refs use REPLACE semantics: omit to keep existing on update, [] to clear, [values] to set.
</purpose>`;
}
get inputSchema() {
return wikiWriteInputSchema;
}
async call(input: WikiWriteInput, context: ToolContext): Promise<ToolOutput<WikiWriteStructured>> {
const wikiService = context.session?.wikiService ?? this.wikiService;
const writesGlobal = !!context.session;
const skipIndex = context.session?.isWorktreeScoped === true;
if (!input.content && (!input.replacements || input.replacements.length === 0)) {
return {
markdown: 'Error: provide either content (for create/rewrite) or replacements (for edits).',
structured: { success: false, key: input.key },
};
}
const scope: BlockScope = writesGlobal ? 'GLOBAL' : 'USER';
const scopeId = scope === 'USER' ? context.userId : null;
const existing = await wikiService.readPage(scope, scopeId, input.key);
if (!existing && !input.content) {
return {
markdown: `Page "${input.key}" does not exist. Provide content to create it.`,
structured: { success: false, key: input.key },
};
}
if (scope === 'USER' && !existing) {
const count = await this.pagesRepository.getUserPageCount(context.userId);
if (count >= MAX_USER_BLOCKS) {
return {
markdown: `Cannot create "${input.key}": user has reached the limit of ${MAX_USER_BLOCKS} pages.`,
structured: { success: false, key: input.key },
};
}
}
const existingFm = existing?.frontmatter;
const resolvedTags = input.tags === undefined ? existingFm?.tags : input.tags;
const resolvedRefs = input.refs === undefined ? existingFm?.refs : input.refs;
const resolvedSlRefs = input.sl_refs === undefined ? existingFm?.sl_refs : input.sl_refs;
let finalContent: string;
const finalFm: WikiFrontmatter = {
summary: input.summary,
usage_mode: existingFm?.usage_mode ?? 'auto',
sort_order: existingFm?.sort_order ?? 0,
tags: resolvedTags,
refs: resolvedRefs,
sl_refs: resolvedSlRefs,
source: input.source === undefined ? existingFm?.source : input.source,
intent: input.intent === undefined ? existingFm?.intent : input.intent,
tables: input.tables === undefined ? existingFm?.tables : input.tables,
representative_sql:
input.representative_sql === undefined ? existingFm?.representative_sql : input.representative_sql,
usage: input.usage === undefined ? existingFm?.usage : input.usage,
fingerprints: input.fingerprints === undefined ? existingFm?.fingerprints : input.fingerprints,
};
if (input.content) {
finalContent = input.content;
} else {
const editResult = applySqlEdits(existing?.content ?? '', input.replacements ?? []);
if (!editResult.success) {
return {
markdown: `Edit errors: ${editResult.errors.join('; ')}`,
structured: { success: false, key: input.key },
};
}
finalContent = editResult.sql;
}
await wikiService.writePage(scope, scopeId, input.key, finalFm, finalContent, SYSTEM_AUTHOR, SYSTEM_EMAIL);
if (!skipIndex) {
await wikiService.syncSinglePage(scope, scopeId, input.key, finalFm, finalContent);
}
await this.knowledgeRepository.createEvent({
blockId: null,
eventType: existing ? 'BLOCK_UPDATED' : 'BLOCK_CREATED',
actorId: context.userId,
chatId: null,
messageId: null,
payload: {
pageKey: input.key,
previousContent: existing ? existing.content.slice(0, 500) : null,
},
});
const action = existing ? 'updated' : 'created';
if (context.session) {
context.session.actions.push({ target: 'wiki', type: action, key: input.key, detail: input.summary });
}
return {
markdown: `Page "${input.key}" ${action}.`,
structured: { success: true, key: input.key, action },
};
}
}