From 7c441b337810ccf41866c4d455809f2ece714f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20M=C3=A1rmol?= Date: Mon, 20 Jul 2026 16:28:04 -0400 Subject: [PATCH] fix: load SKILL.md files with tables SKILL.md files were not being properly loaded when tables were present, since these use the string `"---"` as a delimiter as well. Added a test for this and fixed an integration test's testcase data. --- core/src/skills/loader.ts | 9 ++++++--- core/test/skills/loader_test.ts | 16 ++++++++++++++++ .../integration/skills/loader/events_turn_2.json | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/core/src/skills/loader.ts b/core/src/skills/loader.ts index def2ffa0e..d67c4f087 100644 --- a/core/src/skills/loader.ts +++ b/core/src/skills/loader.ts @@ -118,14 +118,17 @@ export function parseSkillMdContent(content: string): { throw new Error('SKILL.md must start with YAML frontmatter (---)'); } - // Split into max 3 parts: empty before ---, frontmatter, body - const parts = content.split('---', 3); + // Split into at least 3 parts: empty before ---, frontmatter, body + const parts = content.split('---'); if (parts.length < 3) { throw new Error('SKILL.md frontmatter not properly closed with ---'); } const frontmatterStr = parts[1]; - const body = parts[2].trim(); + const body = parts + .filter((_, i) => i >= 2) + .join('---') + .trim(); try { const parsed = yaml.load(frontmatterStr); diff --git a/core/test/skills/loader_test.ts b/core/test/skills/loader_test.ts index 6f9bcaf97..4cd132f12 100644 --- a/core/test/skills/loader_test.ts +++ b/core/test/skills/loader_test.ts @@ -101,6 +101,22 @@ Body with newlines const result = parseSkillMdContent(content); expect(result.body).toBe('Body with newlines'); }); + + it('handles tables in body', () => { + const body = `Body with table + +| Column 1 | Column 2 | +|---|---| +| Cell 1 | Cell 2 |`; + const content = `--- +name: test-skill +description: A test skill +--- +${body} +`; + const result = parseSkillMdContent(content); + expect(result.body).toBe(body); + }); }); describe('loadSkillFromDir', () => { diff --git a/tests/integration/skills/loader/events_turn_2.json b/tests/integration/skills/loader/events_turn_2.json index b85b13d7e..bab945b32 100644 --- a/tests/integration/skills/loader/events_turn_2.json +++ b/tests/integration/skills/loader/events_turn_2.json @@ -52,7 +52,7 @@ "name": "load_skill", "response": { "skill_name": "gws-calendar", - "instructions": "# calendar (v3)\n\n> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it.\n\n```bash\ngws calendar [flags]\n```\n\n## Helper Commands\n\n| Command | Description |\n|", + "instructions": "# calendar (v3)\n\n> **PREREQUISITE:** Read `../gws-shared/SKILL.md` for auth, global flags, and security rules. If missing, run `gws generate-skills` to create it.\n\n```bash\ngws calendar [flags]\n```\n\n## Helper Commands\n\n| Command | Description |\n| -------------------------------------------- | ----------------------------------------- |\n| [`+insert`](../gws-calendar-insert/SKILL.md) | create a new event |\n| [`+agenda`](../gws-calendar-agenda/SKILL.md) | Show upcoming events across all calendars |\n\n## API Resources\n\n### acl\n\n- `delete` — Deletes an access control rule.\n- `get` — Returns an access control rule.\n- `insert` — Creates an access control rule.\n- `list` — Returns the rules in the access control list for the calendar.\n- `patch` — Updates an access control rule. This method supports patch semantics.\n- `update` — Updates an access control rule.\n- `watch` — Watch for changes to ACL resources.\n\n### calendarList\n\n- `delete` — Removes a calendar from the user's calendar list.\n- `get` — Returns a calendar from the user's calendar list.\n- `insert` — Inserts an existing calendar into the user's calendar list.\n- `list` — Returns the calendars on the user's calendar list.\n- `patch` — Updates an existing calendar on the user's calendar list. This method supports patch semantics.\n- `update` — Updates an existing calendar on the user's calendar list.\n- `watch` — Watch for changes to CalendarList resources.\n\n### calendars\n\n- `clear` — Clears a primary calendar. This operation deletes all events associated with the primary calendar of an account.\n- `delete` — Deletes a secondary calendar. Use calendars.clear for clearing all events on primary calendars.\n- `get` — Returns metadata for a calendar.\n- `insert` — Creates a secondary calendar.\n The authenticated user for the request is made the data owner of the new calendar.\n\nNote: We recommend to authenticate as the intended data owner of the calendar. You can use domain-wide delegation of authority to allow applications to act on behalf of a specific user. Don't use a service account for authentication. If you use a service account for authentication, the service account is the data owner, which can lead to unexpected behavior.\n\n- `patch` — Updates metadata for a calendar. This method supports patch semantics.\n- `update` — Updates metadata for a calendar.\n\n### channels\n\n- `stop` — Stop watching resources through this channel\n\n### colors\n\n- `get` — Returns the color definitions for calendars and events.\n\n### events\n\n- `delete` — Deletes an event.\n- `get` — Returns an event based on its Google Calendar ID. To retrieve an event using its iCalendar ID, call the events.list method using the iCalUID parameter.\n- `import` — Imports an event. This operation is used to add a private copy of an existing event to a calendar. Only events with an eventType of default may be imported.\n Deprecated behavior: If a non-default event is imported, its type will be changed to default and any event-type-specific properties it may have will be dropped.\n- `insert` — Creates an event.\n- `instances` — Returns instances of the specified recurring event.\n- `list` — Returns events on the specified calendar.\n- `move` — Moves an event to another calendar, i.e. changes an event's organizer. Note that only default events can be moved; birthday, focusTime, fromGmail, outOfOffice and workingLocation events cannot be moved.\n- `patch` — Updates an event. This method supports patch semantics.\n- `quickAdd` — Creates an event based on a simple text string.\n- `update` — Updates an event.\n- `watch` — Watch for changes to Events resources.\n\n### freebusy\n\n- `query` — Returns free/busy information for a set of calendars.\n\n### settings\n\n- `get` — Returns a single user setting.\n- `list` — Returns all user settings for the authenticated user.\n- `watch` — Watch for changes to Settings resources.\n\n## Discovering Commands\n\nBefore calling any API method, inspect it:\n\n```bash\n# Browse resources and methods\ngws calendar --help\n\n# Inspect a method's required params, types, and defaults\ngws schema calendar..\n```\n\nUse `gws schema` output to build your `--params` and `--json` flags.", "frontmatter": { "name": "gws-calendar", "description": "Google Calendar: Manage calendars and events.",