Skip to content
Open
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
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ cli({
| `select` | Extract JSON path | `select: data.items` |
| `map` | Map fields | `map: { title: "${{ item.title }}" }` |
| `filter` | Filter items | `filter: item.score > 100` |
| `sort` | Sort items | `sort: { by: score, order: desc }` |
| `sort` | Sort items (`numeric: true` for number strings) | `sort: { by: score, order: desc }` |
| `limit` | Cap result count | `limit: ${{ args.limit }}` |
| `intercept` | Declarative XHR capture | `intercept: { trigger: "navigate:...", capture: "api/hot" }` |
| `tap` | Store action + XHR capture | `tap: { store: "feed", action: "fetchFeeds", capture: "homefeed" }` |
Expand Down
8 changes: 6 additions & 2 deletions src/pipeline/steps/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,15 @@ export async function stepSort(_page: IPage | null, params: unknown, data: unkno
if (!Array.isArray(data)) return data;
const key = isRecord(params) ? String(params.by ?? '') : String(params);
const reverse = isRecord(params) ? params.order === 'desc' : false;
// numeric: true -- convert values with Number() before comparison (handles string-encoded numbers)
const numeric = isRecord(params) ? params.numeric === true : false;
return [...data].sort((a, b) => {
const left = isRecord(a) ? a[key] : undefined;
const right = isRecord(b) ? b[key] : undefined;
const va = left ?? '';
const vb = right ?? '';
const na = Number(left);
const nb = Number(right);
const va = numeric ? (Number.isFinite(na) ? na : 0) : (left ?? '');
const vb = numeric ? (Number.isFinite(nb) ? nb : 0) : (right ?? '');
const cmp = va < vb ? -1 : va > vb ? 1 : 0;
return reverse ? -cmp : cmp;
});
Expand Down
51 changes: 51 additions & 0 deletions src/pipeline/transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,57 @@ describe('stepSort', () => {
await stepSort(null, 'score', SAMPLE_DATA, {});
expect(SAMPLE_DATA).toEqual(original);
});

it('sorts string-encoded numbers numerically with numeric: true', async () => {
const data = [
{ name: 'A', volume: '99' },
{ name: 'B', volume: '1000' },
{ name: 'C', volume: '250' },
];
const result = await stepSort(null, { by: 'volume', order: 'desc', numeric: true }, data, {});
expect((result as typeof data).map((r) => r.name)).toEqual(['B', 'C', 'A']);
});

it('sorts string-encoded numbers lexicographically without numeric', async () => {
const data = [
{ name: 'A', volume: '99' },
{ name: 'B', volume: '1000' },
{ name: 'C', volume: '250' },
];
const result = await stepSort(null, { by: 'volume', order: 'desc' }, data, {});
// Lexicographic: "99" > "250" > "1000"
expect((result as typeof data).map((r) => r.name)).toEqual(['A', 'C', 'B']);
});

it('treats non-numeric values as 0 when numeric: true', async () => {
const data = [
{ name: 'A', value: '100' },
{ name: 'B', value: 'N/A' },
{ name: 'C', value: '50' },
];
const result = await stepSort(null, { by: 'value', order: 'asc', numeric: true }, data, {});
expect((result as typeof data).map((r) => r.name)).toEqual(['B', 'C', 'A']);
});

it('handles "0" and negative numbers correctly with numeric: true', async () => {
const data = [
{ name: 'A', value: '-10' },
{ name: 'B', value: '0' },
{ name: 'C', value: '5' },
];
const result = await stepSort(null, { by: 'value', order: 'asc', numeric: true }, data, {});
expect((result as typeof data).map((r) => r.name)).toEqual(['A', 'B', 'C']);
});

it('treats missing fields as 0 when numeric: true', async () => {
const data = [
{ name: 'A', value: '10' },
{ name: 'B' },
{ name: 'C', value: '-5' },
];
const result = await stepSort(null, { by: 'value', order: 'asc', numeric: true }, data, {});
expect((result as typeof data).map((r) => r.name)).toEqual(['C', 'B', 'A']);
});
});

describe('stepLimit', () => {
Expand Down
Loading