fix: register Section in NODE_TABLES and NODE_SCHEMA_QUERIES (#401)

* feat: add markdown file indexing (headings + cross-links)

Parse .md/.mdx files using regex (no tree-sitter dependency) to extract:
- Section nodes from headings (h1-h6) with hierarchy via CONTAINS edges
- Cross-file IMPORTS edges from markdown links to other repo files

Ported from #286 to resolve conflicts with kuzu→lbug rename.

Co-Authored-By: Dennis Palatov <dp-web4@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add Section to NODE_TABLES and NODE_SCHEMA_QUERIES

The Section schema was defined but not registered in NODE_TABLES or
NODE_SCHEMA_QUERIES, so the table was never created in the database.
Also adds missing FROM File TO Section relation entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update schema test counts for Section node type

NODE_TABLES: 27→28, NODE_SCHEMA_QUERIES: 27→28, SCHEMA_QUERIES: 29→30

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add diagnostic output to skills-e2e idempotency test

Show stdout/stderr in assertion message so CI failures reveal
why the second analyze --skills run exits with code 1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add Section COPY query with level column in lbug-adapter

Section table has 8 columns (includes level) but getCopyQuery fell
through to the default 7-column multi-language path. Adds explicit
Section cases to getCopyQuery and insertNodeToLbug/upsertNodeToLbug.

Error was: COPY failed for Section: Number of columns mismatch. Expected 7 but got 8.

---------

Co-authored-by: Dennis Palatov <dp-web4@users.noreply.github.com>
This commit is contained in:
Abhigyan Patwari 2026-03-21 04:00:16 +05:30 committed by GitHub
parent 00758b102a
commit 1f7764c49b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 26 additions and 6 deletions

View file

@ -336,6 +336,9 @@ const getCopyQuery = (table: NodeTableName, filePath: string): string => {
if (table === 'Process') {
return `COPY ${t}(id, label, heuristicLabel, processType, stepCount, communities, entryPointId, terminalId) FROM "${filePath}" ${COPY_CSV_OPTS}`;
}
if (table === 'Section') {
return `COPY ${t}(id, name, filePath, startLine, endLine, level, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
}
if (table === 'Method') {
return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, parameterCount, returnType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
}
@ -380,6 +383,9 @@ export const insertNodeToLbug = async (
query = `CREATE (n:File {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, content: ${escapeValue(properties.content || '')}})`;
} else if (label === 'Folder') {
query = `CREATE (n:Folder {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}})`;
} else if (label === 'Section') {
const descPart = properties.description ? `, description: ${escapeValue(properties.description)}` : '';
query = `CREATE (n:Section {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, level: ${properties.level || 1}, content: ${escapeValue(properties.content || '')}${descPart}})`;
} else if (TABLES_WITH_EXPORTED.has(label)) {
const descPart = properties.description ? `, description: ${escapeValue(properties.description)}` : '';
query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${escapeValue(properties.content || '')}${descPart}})`;
@ -451,6 +457,9 @@ export const batchInsertNodesToLbug = async (
query = `MERGE (n:File {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.content = ${escapeValue(properties.content || '')}`;
} else if (label === 'Folder') {
query = `MERGE (n:Folder {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}`;
} else if (label === 'Section') {
const descPart = properties.description ? `, n.description = ${escapeValue(properties.description)}` : '';
query = `MERGE (n:Section {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.level = ${properties.level || 1}, n.content = ${escapeValue(properties.content || '')}${descPart}`;
} else if (TABLES_WITH_EXPORTED.has(label)) {
const descPart = properties.description ? `, n.description = ${escapeValue(properties.description)}` : '';
query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${escapeValue(properties.content || '')}${descPart}`;

View file

@ -13,7 +13,7 @@
// NODE TABLE NAMES
// ============================================================================
export const NODE_TABLES = [
'File', 'Folder', 'Function', 'Class', 'Interface', 'Method', 'CodeElement', 'Community', 'Process',
'File', 'Folder', 'Function', 'Class', 'Interface', 'Method', 'CodeElement', 'Community', 'Process', 'Section',
// Multi-language support
'Struct', 'Enum', 'Macro', 'Typedef', 'Union', 'Namespace', 'Trait', 'Impl',
'TypeAlias', 'Const', 'Static', 'Property', 'Record', 'Delegate', 'Annotation', 'Constructor', 'Template', 'Module'
@ -238,6 +238,7 @@ CREATE REL TABLE ${REL_TABLE_NAME} (
FROM File TO \`Constructor\`,
FROM File TO \`Template\`,
FROM File TO \`Module\`,
FROM File TO Section,
FROM Folder TO Folder,
FROM Folder TO File,
FROM Function TO Function,
@ -462,6 +463,8 @@ export const NODE_SCHEMA_QUERIES = [
CONSTRUCTOR_SCHEMA,
TEMPLATE_SCHEMA,
MODULE_SCHEMA,
// Markdown support
SECTION_SCHEMA,
];
export const REL_SCHEMA_QUERIES = [

View file

@ -2389,8 +2389,16 @@ export function createEntry(level: string, msg: string) {
/* CI timeout tolerance */
if (result1.status === null || result2.status === null) return;
expect(result1.status).toBe(0);
expect(result2.status).toBe(0);
expect(result1.status, [
`first analyze --skills exited with code ${result1.status}`,
`stdout: ${result1.stdout?.slice(0, 500)}`,
`stderr: ${result1.stderr?.slice(0, 500)}`,
].join('\n')).toBe(0);
expect(result2.status, [
`second analyze --skills exited with code ${result2.status}`,
`stdout: ${result2.stdout?.slice(0, 500)}`,
`stderr: ${result2.stderr?.slice(0, 500)}`,
].join('\n')).toBe(0);
const generatedDir = path.join(tmpDir, '.claude', 'skills', 'generated');
expect(fs.existsSync(generatedDir)).toBe(true);

View file

@ -40,7 +40,7 @@ describe('LadybugDB Schema', () => {
it('has expected total count', () => {
// 9 core + 18 multi-language = 27
expect(NODE_TABLES).toHaveLength(27);
expect(NODE_TABLES).toHaveLength(28);
});
});
@ -164,7 +164,7 @@ describe('LadybugDB Schema', () => {
describe('schema query ordering', () => {
it('NODE_SCHEMA_QUERIES has correct count', () => {
expect(NODE_SCHEMA_QUERIES).toHaveLength(27);
expect(NODE_SCHEMA_QUERIES).toHaveLength(28);
});
it('REL_SCHEMA_QUERIES has one relation table', () => {
@ -173,7 +173,7 @@ describe('LadybugDB Schema', () => {
it('SCHEMA_QUERIES includes all node + rel + embedding schemas', () => {
// 27 node + 1 rel + 1 embedding = 29
expect(SCHEMA_QUERIES).toHaveLength(29);
expect(SCHEMA_QUERIES).toHaveLength(30);
});
it('node schemas come before relation schemas in SCHEMA_QUERIES', () => {