Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/fix-routing-strip-example-quotes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@bradygaster/squad-sdk": patch
---

Fix: strip surrounding quotes from routing.md example phrases in `parseRoutingMarkdown`

`parseRoutingMarkdown` split the Examples cell on commas but never stripped the surrounding quotes, so a quoted example like `"unit tests"` was stored verbatim (quotes included). When `compileRoutingRules` / `matchRoute` later tokenized it, the quote characters glued onto the boundary words (`"unit`, `tests"`), producing regex patterns that could never match — so routing tables that used quoted examples silently routed every message to the fallback agent.

Leading/trailing quotes (`"`, `'`, `` ` ``) are now stripped from each example, so quoted and unquoted examples behave identically. Adds regression tests covering both the parse step and end-to-end `matchRoute`.
11 changes: 10 additions & 1 deletion packages/squad-sdk/src/config/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,16 @@ export function parseRoutingMarkdown(content: string): RoutingConfig {
if (cells.length >= 2) {
const workType = cells[0];
const routeTo = cells[1]!;
const examples = cells.length >= 3 ? cells[2]!.split(',').map(ex => ex.trim()) : undefined;
// Strip surrounding quotes (", ', `) so quoted and unquoted examples
// tokenize identically. Without this, a quoted example like "unit tests"
// keeps its quotes and compiles to patterns that never match, which made
// quoted routing examples silently route everything to fallback.
const examples = cells.length >= 3
? cells[2]!
.split(',')
.map(ex => ex.trim().replace(/^["'`]+|["'`]+$/g, '').trim())
.filter(ex => ex.length > 0)
: undefined;

// Parse agent names (may be comma-separated or single)
const agents = routeTo.split(',').map(a => a.trim()).filter(a => a.length > 0);
Expand Down
28 changes: 28 additions & 0 deletions test/routing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ describe('parseRoutingMarkdown', () => {
expect(config.rules[1].agents).toEqual(['Developer']);
});

it('strips surrounding quotes from examples', () => {
const markdown = `
## Routing Table

| Work Type | Route To | Examples |
|-----------|----------|----------|
| testing | Tester | "unit tests", "jest coverage" |
`;

const config = parseRoutingMarkdown(markdown);

expect(config.rules[0].examples).toEqual(['unit tests', 'jest coverage']);
});

it('handles multiple agents per rule', () => {
const markdown = `
## Routing Table
Expand Down Expand Up @@ -200,6 +214,20 @@ describe('matchRoute', () => {
expect(match.rule?.workType).toBe('bug-fix');
});

it('matches quoted examples after quote-stripping (regression)', () => {
const md = `
## Routing Table

| Work Type | Route To | Examples |
|-----------|----------|----------|
| testing | Tester | "unit tests", "jest coverage" |
`;
const quotedRouter = compileRoutingRules(parseRoutingMarkdown(md));
const match = matchRoute('please write unit tests now', quotedRouter);

expect(match.agents).toEqual(['Tester']);
});

it('returns fallback for no match', () => {
const match = matchRoute('what is the weather today?', router);

Expand Down
Loading