From c73af33fc3e9b8d461fe437a0ab800369c61864d Mon Sep 17 00:00:00 2001 From: root Date: Sun, 24 May 2026 17:52:02 +0000 Subject: [PATCH] feat: parseResolverEntries supports list-based resolver format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenClaw compact resolver format uses markdown lists instead of tables: - **skill-name**: trigger1 | trigger2 | trigger3 - skill-name: trigger phrase This is the format OpenClaw agents actually use. The table format (| trigger | skill path |) continues to work — both formats are parsed in the same pass. Handles: - Bold skill names: - **name**: triggers - Plain skill names: - name: triggers - Pipe-delimited triggers: trigger1 | trigger2 | trigger3 - Optional path suffix: → `skills//SKILL.md` (stripped) - Ellipsis markers: ... (filtered out) - Section headings: ## Section Name (tracked as before) --- src/core/check-resolvable.ts | 59 ++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/core/check-resolvable.ts b/src/core/check-resolvable.ts index bb1249d1c..bde591109 100644 --- a/src/core/check-resolvable.ts +++ b/src/core/check-resolvable.ts @@ -127,29 +127,50 @@ export function parseResolverEntries(resolverContent: string): ResolverEntry[] { continue; } - // Skip non-table rows - if (!line.startsWith('|') || line.includes('---')) continue; - - // Split table columns - const cols = line.split('|').map(c => c.trim()).filter(Boolean); - if (cols.length < 2) continue; - - const trigger = cols[0]; - const skillCol = cols[1]; - - // Skip header rows - if (trigger.toLowerCase() === 'trigger' || trigger.toLowerCase() === 'skill') continue; + // ── Format 1: Markdown table rows ── + // | trigger phrase | `skills//SKILL.md` | + if (line.startsWith('|') && !line.includes('---')) { + const cols = line.split('|').map(c => c.trim()).filter(Boolean); + if (cols.length < 2) continue; + + const trigger = cols[0]; + const skillCol = cols[1]; + + // Skip header rows + if (trigger.toLowerCase() === 'trigger' || trigger.toLowerCase() === 'skill') continue; + + // Check for GStack entries + if (skillCol.startsWith('GStack:') || skillCol.startsWith('Check ') || skillCol.startsWith('Read ')) { + entries.push({ trigger, skillPath: skillCol, isGStack: true, section: currentSection }); + continue; + } - // Check for GStack entries - if (skillCol.startsWith('GStack:') || skillCol.startsWith('Check ') || skillCol.startsWith('Read ')) { - entries.push({ trigger, skillPath: skillCol, isGStack: true, section: currentSection }); + // Extract skill path from backtick-wrapped references + const pathMatch = skillCol.match(/`(skills\/[^`]+\/SKILL\.md)`/); + if (pathMatch) { + entries.push({ trigger, skillPath: pathMatch[1], isGStack: false, section: currentSection }); + } continue; } - // Extract skill path from backtick-wrapped references - const pathMatch = skillCol.match(/`(skills\/[^`]+\/SKILL\.md)`/); - if (pathMatch) { - entries.push({ trigger, skillPath: pathMatch[1], isGStack: false, section: currentSection }); + // ── Format 2: List-based resolver (OpenClaw compact format) ── + // - **skill-name**: trigger1 | trigger2 | trigger3 + // - skill-name: trigger1 | trigger2 | trigger3 + const listBold = line.match(/^-\s+\*\*([^*]+)\*\*\s*:\s*(.+)/); + const listPlain = !listBold ? line.match(/^-\s+([\w-]+)\s*:\s*(.+)/) : null; + const listMatch = listBold || listPlain; + if (listMatch) { + const skillName = listMatch[1].trim(); + const triggersRaw = listMatch[2].trim(); + // Remove optional trailing path reference: → `skills//SKILL.md` + const cleaned = triggersRaw.replace(/\s*→\s*`skills\/[^`]+`/, ''); + // Split on | delimiter + const triggers = cleaned.split('|').map(t => t.trim()).filter(t => t.length > 0 && t !== '...'); + const skillPath = `skills/${skillName}/SKILL.md`; + for (const trigger of triggers) { + entries.push({ trigger, skillPath, isGStack: false, section: currentSection }); + } + continue; } }