Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions __tests__/info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import { describe, it, expect } from 'vitest';
import { info } from '../src/api';

describe('info API', () => {
it('should return skill info for default library', () => {
it('should return doc info for default library', () => {
const result = info();
expect(result).toBeDefined();
expect(result!.name).toBe('antv-g2-chart');
expect(result!.description.length).toBeGreaterThan(0);
expect(result!.content.length).toBeGreaterThan(0);
});

it('should return skill info for g2 library', () => {
it('should return doc info for g2 library', () => {
const result = info('g2');
expect(result).toBeDefined();
expect(result).toHaveProperty('name');
expect(result).toHaveProperty('description');
expect(result).toHaveProperty('content');
});

it('should return undefined for library without SKILL.md', () => {
it('should return undefined for library without DOC.md', () => {
const result = info('g6');
// g6 may or may not have a SKILL.md
expect(result === undefined || typeof result.name === 'string').toBe(true);
Comment thread
lxfu1 marked this conversation as resolved.
Expand Down
2 changes: 1 addition & 1 deletion __tests__/retrieve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('retrieve API', () => {
}
};

it('should retrieve skills with default parameters', async () => {
it('should retrieve docs with default parameters', async () => {
const results = await retrieve('折线图');
// Defaults: content=true, includeConstraints=true
if (zvecReady) {
Expand Down
69 changes: 35 additions & 34 deletions __tests__/token-budget.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
formatForBudget,
applyTokenBudget
} from '../src/core/token-budget';
import type { Skill } from '../src/core/types';
import type { Doc } from '../src/core/types';

describe('token-budget', () => {
describe('estimateTokens', () => {
Expand Down Expand Up @@ -34,7 +34,8 @@ describe('token-budget', () => {

describe('extractCodeBlocks', () => {
it('should extract fenced code blocks from markdown', () => {
const content = 'Some text\n```js\nconst x = 1;\n```\nMore text\n```ts\nconst y = 2;\n```';
const content =
'Some text\n```js\nconst x = 1;\n```\nMore text\n```ts\nconst y = 2;\n```';
const blocks = extractCodeBlocks(content);
expect(blocks).toHaveLength(2);
expect(blocks[0]).toContain('const x = 1');
Expand Down Expand Up @@ -70,10 +71,9 @@ describe('token-budget', () => {
});

describe('formatForBudget', () => {
const baseSkill: Skill = {
id: 'test-skill',
title: 'Test Skill',
title_en: 'Test Skill',
const baseDoc: Doc = {
id: 'test-doc',
title: 'Test Doc',
description: 'A test description',
library: 'g2',
version: '5.x',
Expand All @@ -83,47 +83,47 @@ describe('token-budget', () => {
use_cases: [],
anti_patterns: [],
related: [],
content: '## Intro\nSome text\n```js\nconst x = 1;\n```\nMore details here.'
content:
'## Intro\nSome text\n```js\nconst x = 1;\n```\nMore details here.'
};

it('level 0: should return full content', () => {
const result = formatForBudget(baseSkill, 0, 500);
expect(result).toContain('Test Skill');
const result = formatForBudget(baseDoc, 0, 500);
expect(result).toContain('Test Doc');
expect(result).toContain('Some text');
expect(result).toContain('const x = 1');
expect(result).toContain('More details');
});

it('level 1: should return summary + code blocks only', () => {
const result = formatForBudget(baseSkill, 1, 500);
expect(result).toContain('Test Skill');
const result = formatForBudget(baseDoc, 1, 500);
expect(result).toContain('Test Doc');
expect(result).toContain('A test description');
expect(result).toContain('const x = 1');
// Should NOT contain the prose "More details" (it's not in a code block)
expect(result).not.toContain('More details');
});

it('level 2: should return summary only', () => {
const result = formatForBudget(baseSkill, 2, 500);
expect(result).toContain('Test Skill');
const result = formatForBudget(baseDoc, 2, 500);
expect(result).toContain('Test Doc');
expect(result).toContain('A test description');
// Should NOT contain any body content
expect(result).not.toContain('Some text');
expect(result).not.toContain('const x = 1');
});

it('should truncate when budget is too small for code blocks', () => {
const result = formatForBudget(baseSkill, 1, 5);
const result = formatForBudget(baseDoc, 1, 5);
// Very small budget — should fallback to truncateContent
expect(result.length).toBeGreaterThan(0);
});
});

describe('applyTokenBudget', () => {
const makeSkill = (id: string, content: string): Skill => ({
const makeDoc = (id: string, content: string): Doc => ({
id,
title: `Skill ${id}`,
title_en: `Skill ${id}`,
title: `Doc ${id}`,
description: '',
library: 'g2',
version: '',
Expand All @@ -136,44 +136,45 @@ describe('token-budget', () => {
content
});

it('should give full budget to __info__ and trim remaining skills', () => {
const skills: Skill[] = [
it('should give full budget to __info__ and trim remaining docs', () => {
const docs: Doc[] = [
{
...makeSkill('__info__g2', 'Core constraints text that is important'),
...makeDoc('__info__g2', 'Core constraints text that is important'),
category: '__info__'
},
makeSkill('skill-1', 'Content for skill one'),
makeSkill('skill-2', 'Content for skill two'),
makeDoc('doc-1', 'Content for doc one'),
makeDoc('doc-2', 'Content for doc two')
];

const result = applyTokenBudget(skills, 20, 1);
const result = applyTokenBudget(docs, 20, 1);
// __info__ should keep its content
expect(result[0].content).toBeDefined();
});

it('should set content to undefined when budget exhausted', () => {
const skills: Skill[] = [
const docs: Doc[] = [
{
...makeSkill('__info__g2', 'Very long constraints content eating all budget'),
...makeDoc(
'__info__g2',
'Very long constraints content eating all budget'
),
category: '__info__'
},
makeSkill('skill-1', 'Short content'),
makeSkill('skill-2', 'Another content'),
makeDoc('doc-1', 'Short content'),
makeDoc('doc-2', 'Another content')
];

// Very small budget — info consumes it all
const result = applyTokenBudget(skills, 5, 1);
// Skills after info should have no content
const result = applyTokenBudget(docs, 5, 1);
// Docs after info should have no content
expect(result[1].content).toBeUndefined();
expect(result[2].content).toBeUndefined();
});

it('should handle no __info__ skill gracefully', () => {
const skills: Skill[] = [
makeSkill('skill-1', 'Content for skill one'),
];
it('should handle no __info__ doc gracefully', () => {
const docs: Doc[] = [makeDoc('doc-1', 'Content for doc one')];

const result = applyTokenBudget(skills, 50, 0);
const result = applyTokenBudget(docs, 50, 0);
expect(result[0].content).toBeDefined();
});
});
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@antv/chart-visualization-skills",
"version": "0.1.3",
"version": "0.1.4-beta.1",
"description": "Chart visualization skills for AntV, can be used in various scenarios such as data analysis, business intelligence, and dashboard development. Skills and CLI are included.",
"type": "commonjs",
"main": "dist/api.js",
Expand Down Expand Up @@ -48,5 +48,9 @@
},
"author": "AntV AI Team",
"license": "MIT",
"repository": "git@github.com:antvis/chart-visualization-skills.git"
"repository": "git@github.com:antvis/chart-visualization-skills.git",
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
}
}
48 changes: 24 additions & 24 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,69 @@
import {
retrieve as _retrieve,
getSkillById as _getSkillById,
getSkillInfo,
getDocById as _getDocById,
getDocInfo,
availableLibraries,
listSkills as _listSkills
listDocs as _listDocs
} from './core/retriever';
import type {
Skill,
SkillInfo,
Doc,
DocInfo,
RetrieveOptions,
ListOptions
} from './core/types';

export type { Skill, SkillInfo, RetrieveOptions, ListOptions };
export type { Doc, DocInfo, RetrieveOptions, ListOptions };
Comment thread
lxfu1 marked this conversation as resolved.

/**
* Retrieve skills based on a query. Returns skill content by default.
* Retrieve docs based on a query. Returns doc content by default.
*
* @example retrieve('bar chart', { library: 'g2', topK: 5 })
* @example retrieve('bar chart', { library: 'g2', content: false }) // metadata only
*
* Legacy positional signature still supported for backwards compatibility.
* @example retrieve('bar chart', 'g2', 5, true)
*/
export function retrieve(query: string, options?: RetrieveOptions): Promise<Skill[]>;
export function retrieve(query: string, options?: RetrieveOptions): Promise<Doc[]>;
/** @deprecated Use the options-object overload instead. */
export function retrieve(
query: string,
library?: string,
topk?: number,
content?: boolean
): Promise<Skill[]>;
): Promise<Doc[]>;
export async function retrieve(
query: string,
libraryOrOpts?: string | RetrieveOptions,
topk = 7,
content = true
): Promise<Skill[]> {
): Promise<Doc[]> {
if (typeof libraryOrOpts === 'string' || libraryOrOpts === undefined) {
return await _retrieve(query, { library: libraryOrOpts, topK: topk, content });
}
return await _retrieve(query, libraryOrOpts);
}

/**
* Get a single skill by its exact ID.
* Get a single doc by its exact ID.
*
* @param id The skill ID (e.g. 'g2-mark-bar').
* @param id The doc ID (e.g. 'g2-mark-bar').
* @param library Optional: restrict the search to a specific library.
* @returns The skill with full content, or undefined if not found.
* @example getSkillById('g2-mark-bar')
* @returns The doc with full content, or undefined if not found.
* @example getDocById('g2-mark-bar')
*/
export function getSkillById(id: string, library?: string): Skill | undefined {
return _getSkillById(id, library);
export function getDocById(id: string, library?: string): Doc | undefined {
return _getDocById(id, library);
}
Comment thread
lxfu1 marked this conversation as resolved.

/**
* Get skill info embedded in the library index.
* Get doc info embedded in the library index.
*
* @param library The library to get info for (default: 'g2').
* @example info('g2')
* @returns The skill info, or undefined if not available.
* @returns The doc info, or undefined if not available.
*/
export function info(library = 'g2'): SkillInfo | undefined {
return getSkillInfo(library);
export function info(library = 'g2'): DocInfo | undefined {
return getDocInfo(library);
}

/**
Expand All @@ -75,10 +75,10 @@ export function libraries(): string[] {
}

/**
* List available skills, optionally filtered by library, category or tags.
* List available docs, optionally filtered by library, category or tags.
* @param options Filter options.
* @example listSkills({ library: 'g2', tags: ['bar'] })
* @example listDocs({ library: 'g2', tags: ['bar'] })
*/
export function listSkills(options: ListOptions = {}): Skill[] {
return _listSkills(options);
export function listDocs(options: ListOptions = {}): Doc[] {
return _listDocs(options);
}
Comment thread
lxfu1 marked this conversation as resolved.
40 changes: 20 additions & 20 deletions src/commands/get.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
import { Command } from 'commander';
import { getSkillById } from '../core/retriever';
import { getDocById } from '../core/retriever';

export function registerGetCommand(program: Command): void {
program
.command('get <id>')
.description('Get a skill by its exact ID')
.description('Get a doc by its exact ID')
.option('--library <lib>', 'Restrict search to a specific library')
.option('--output <format>', 'Output format: json | text', 'text')
.action((id: string, opts: { library?: string; output: string }) => {
const skill = getSkillById(id, opts.library);
const doc = getDocById(id, opts.library);

if (!skill) {
if (!doc) {
const hint = opts.library ? ` in library "${opts.library}"` : '';
console.error(`Skill not found: "${id}"${hint}`);
console.error('Tip: run `antv list` to browse available skill IDs.');
console.error(`Doc not found: "${id}"${hint}`);
console.error('Tip: run `antv list` to browse available doc IDs.');
process.exit(1);
}

if (opts.output === 'json') {
console.log(JSON.stringify(skill, null, 2));
console.log(JSON.stringify(doc, null, 2));
return;
}

console.log(`${'─'.repeat(50)}`);
console.log(`${skill.title} (${skill.id})`);
console.log(`Library : ${skill.library} v${skill.version}`);
console.log(`${doc.title} (${doc.id})`);
console.log(`Library : ${doc.library} v${doc.version}`);
console.log(
`Category : ${skill.category}${skill.subcategory ? '/' + skill.subcategory : ''}`
`Category : ${doc.category}${doc.subcategory ? '/' + doc.subcategory : ''}`
);
console.log(`Tags : ${skill.tags.join(', ')}`);
console.log(`Desc : ${skill.description}`);
if (skill.use_cases.length)
console.log(`Cases : ${skill.use_cases.join(' / ')}`);
if (skill.anti_patterns.length)
console.log(`Avoid : ${skill.anti_patterns.join(' / ')}`);
if (skill.related.length)
console.log(`Related : ${skill.related.join(', ')}`);
if (skill.content) {
console.log(`\n${skill.content}`);
console.log(`Tags : ${doc.tags.join(', ')}`);
console.log(`Desc : ${doc.description}`);
if (doc.use_cases.length)
console.log(`Cases : ${doc.use_cases.join(' / ')}`);
if (doc.anti_patterns.length)
console.log(`Avoid : ${doc.anti_patterns.join(' / ')}`);
if (doc.related.length)
console.log(`Related : ${doc.related.join(', ')}`);
if (doc.content) {
console.log(`\n${doc.content}`);
}
});
}
Loading
Loading