mirror of
https://github.com/antvis/chart-visualization-skills.git
synced 2026-09-14 15:58:00 +08:00
chore: cli retrieve 时新增 constraints 内容 (#79)
* chore: cli retrieve 时新增 constraints 内容 * chore: readme 更新
This commit is contained in:
@@ -55,11 +55,14 @@ Skills are the **single source of truth** for chart generation knowledge.
|
||||
|
||||
### 2. CLI Tool (`src/`)
|
||||
|
||||
The build script (`src/scripts/build.ts`) parses all skill markdown files and generates JSON index files (`src/index/*.index.json`). The CLI (`antv` command) provides three commands:
|
||||
The build script (`src/scripts/build.ts`) parses all skill markdown files and generates JSON index files (`src/index/*.index.json`). Each index stores two things: the `skills[]` array (reference docs) and `info` (SKILL.md metadata including `constraintsContent` — the core constraints section up to `<!-- CONSTRAINTS:END -->`).
|
||||
|
||||
- `antv retrieve <query>` - BM25 full-text search over skills
|
||||
The CLI (`antv` command) provides four commands:
|
||||
|
||||
- `antv retrieve <query>` - BM25 full-text search over skills; `--content` returns reference doc markdown and auto-prepends the core constraints block as the first result
|
||||
- `antv get <id>` - Get a single skill by exact ID
|
||||
- `antv list` - List/filter available skills
|
||||
- `antv info <library>` - Show library metadata
|
||||
- `antv info <library>` - Show library core constraints (SKILL.md Section 1-2)
|
||||
|
||||
The retrieval engine (`src/core/bm25.ts`) implements BM25 with Chinese/English tokenization, synonym expansion, and chart-type boosting.
|
||||
|
||||
@@ -100,8 +103,8 @@ Iterates until MAX_PASSES consecutive clean passes are achieved.
|
||||
Next.js web app for interactive chart generation. Dual-panel UI with chat interface, code editor (Monaco), and real-time chart preview.
|
||||
|
||||
Two retrieval modes:
|
||||
- **Skill mode** - Agent calls `load_skill` / `read_file` tools
|
||||
- **CLI mode** - Pre-injected BM25 results in system prompt
|
||||
- **Skill mode** - Agent calls `load_skill` / `read_file` tools to load SKILL.md and reference docs on demand
|
||||
- **CLI mode** - Agent calls `info` (first turn, gets core constraints) and `retrieve` tools (each turn, gets BM25-matched reference docs with constraints auto-prepended)
|
||||
|
||||
## Project Structure
|
||||
|
||||
|
||||
@@ -93,19 +93,25 @@ npm install -g @antv/chart-visualization-skills
|
||||
**Retrieve or list skills by query**:
|
||||
|
||||
```bash
|
||||
# Retrieve skills by query
|
||||
antv retrieve "bar chart" --library g2 --topk 10 --content
|
||||
# Retrieve skills by query (metadata only)
|
||||
antv retrieve "bar chart" --library g2 --topk 10
|
||||
|
||||
# Retrieve skills with full markdown content (core constraints auto-prepended)
|
||||
antv retrieve "bar chart" --library g2 --content
|
||||
|
||||
# Retrieve skills and output as JSON
|
||||
antv retrieve "bar chart" --library g2 --output json
|
||||
|
||||
# Get a skill by its exact ID
|
||||
antv get g2-mark-interval-basic --library g2
|
||||
|
||||
# List all available skills
|
||||
antv list --library g2 --category core
|
||||
|
||||
# List skills and output as JSON
|
||||
antv list --output json
|
||||
|
||||
# Show skill info
|
||||
# Show skill info (core constraints from SKILL.md)
|
||||
antv info --library g2
|
||||
|
||||
# Show skill info as JSON
|
||||
@@ -130,43 +136,69 @@ Commands:
|
||||
info [options] Show skill info from SKILL.md
|
||||
help [command] display help for command
|
||||
|
||||
Options shared by all commands:
|
||||
Options for retrieve:
|
||||
--library <lib> Filter by library (e.g. g2, g6)
|
||||
--topk <n> Number of results to return (default: 7)
|
||||
--content Include markdown content body in results; core constraints (SKILL.md Section 1-2) are always prepended as the first result
|
||||
--output <format> Output format: json | text (default: "text")
|
||||
```
|
||||
|
||||
> Note: `--content` always prepends the library's core constraints (Section 1 & 2 of SKILL.md, up to the `<!-- CONSTRAINTS:END -->` marker) as the first result, ensuring the model receives essential rules alongside the reference documents.
|
||||
|
||||
### API Usage
|
||||
|
||||
```typescript
|
||||
import { retrieve } from '@antv/chart-visualization-skills';
|
||||
|
||||
const skills = retrieve('bar chart', 'g2', 5);
|
||||
// with content body: retrieve('bar chart', 'g2', 5, true)
|
||||
// Metadata only (no content)
|
||||
const skills = retrieve('bar chart', { library: 'g2', topK: 5 });
|
||||
|
||||
// With full markdown content (core constraints auto-prepended as first result)
|
||||
const skills = retrieve('bar chart', { library: 'g2', topK: 5, content: true });
|
||||
|
||||
// With content but without core constraints
|
||||
const skills = retrieve('bar chart', { library: 'g2', topK: 5, content: true, includeInfo: false });
|
||||
```
|
||||
|
||||
```typescript
|
||||
retrieve(query: string, library?: string, topk?: number, content?: boolean)
|
||||
retrieve(query: string, options?: RetrieveOptions): Skill[]
|
||||
|
||||
interface RetrieveOptions {
|
||||
library?: string; // Library filter, e.g. 'g2' or 'g6'
|
||||
topK?: number; // Number of results (default: 7)
|
||||
content?: boolean; // Include markdown content body (default: false)
|
||||
includeInfo?: boolean; // Prepend SKILL.md core constraints (default: same as content)
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| Option | Type | Default | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `query` | `string` | — | Search query |
|
||||
| `library` | `string` | `'g2'` | Library filter (`g2` or `g6`) |
|
||||
| `topk` | `number` | `7` | Number of results |
|
||||
| `content` | `boolean` | `false` | Whether to include markdown content |
|
||||
| `library` | `string` | all | Library filter (`g2` or `g6`) |
|
||||
| `topK` | `number` | `7` | Number of results |
|
||||
| `content` | `boolean` | `false` | Include markdown content body |
|
||||
| `includeInfo` | `boolean` | same as `content` | Prepend SKILL.md core constraints (Section 1-2) as first result |
|
||||
|
||||
> Notes:
|
||||
> - Default retrieval returns lightweight result objects without the `content` field.
|
||||
> - `content = true` returns markdown content body (frontmatter metadata is excluded).
|
||||
> - `content: true` returns markdown content body (frontmatter metadata is excluded).
|
||||
> - When `includeInfo` is true (the default when `content: true`), the core constraints block — SKILL.md up to `<!-- CONSTRAINTS:END -->` — is injected as the first element (id prefixed with `__info__`), ensuring the model always sees the essential rules.
|
||||
|
||||
```typescript
|
||||
import { info } from '@antv/chart-visualization-skills';
|
||||
|
||||
const skillInfo = info('g2');
|
||||
// => { name: 'antv-g2-chart', description: '...', content: '...' }
|
||||
// => { name: 'antv-g2-chart', description: '...', content: '...', constraintsContent: '...' }
|
||||
```
|
||||
|
||||
```typescript
|
||||
info(library?: string): SkillInfo | undefined
|
||||
|
||||
interface SkillInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
content: string; // Full SKILL.md body (after frontmatter)
|
||||
constraintsContent: string; // SKILL.md body up to <!-- CONSTRAINTS:END --> marker; injected by retrieve when includeInfo: true
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { getLibraryDisplayName } from './util';
|
||||
import { createInfoTool } from './tools/info-tool';
|
||||
import { createRetrieveTool } from './tools/retrieve-tool';
|
||||
|
||||
export function buildCliSystemPrompt(library: string): string {
|
||||
const libraryName = getLibraryDisplayName(library);
|
||||
return `你是 AntV ${libraryName} v5 专家。你可以使用以下工具获取技术文档内容,帮你完成任务:
|
||||
- 调用 \`info\`,获取当前图表库相关信息与要求文档;
|
||||
- 调用 \`retrieve\`,通过用户需求或检索关键词,召回最相关的参考文档,支持设置召回文档数量;
|
||||
- 调用 \`retrieve\`,通过用户需求或检索关键词,召回最相关的参考文档;结果首位自动包含核心约束(使用规则、禁止写法、常见错误),无需单独获取;
|
||||
|
||||
## 工作流程
|
||||
|
||||
首轮一定要先调用 info 获取可视化库的基础信息,然后每轮都调用 retrieve 召回相关文档,再基于召回内容生成可运行的完整图表代码,遵从召回文档中的注意事项。
|
||||
每轮调用 \`retrieve\` 召回与当前需求最相关的参考文档,再基于召回内容生成可运行的完整图表代码,遵从文档中的注意事项。
|
||||
|
||||
**重要**:每次用户提出新需求或修改请求时,你都必须重新调用 \`retrieve\` 召回与当前需求最相关的参考文档,不要依赖之前轮次的召回结果。不同的需求需要不同的参考文档。
|
||||
**重要**:每次用户提出新需求或修改请求时,必须重新调用 \`retrieve\`,不要依赖之前轮次的召回结果。
|
||||
|
||||
## Output Format
|
||||
|
||||
@@ -21,12 +19,11 @@ export function buildCliSystemPrompt(library: string): string {
|
||||
3. \`container\` 必须为 'container'
|
||||
4. 代码末尾必须有 \`chart.render();\`
|
||||
5. 禁止返回 HTML 代码
|
||||
6. 关键配置处可加简短注释,但不要过度注释;`;
|
||||
6. 关键配置处可加简短注释,但不要过度注释`;
|
||||
}
|
||||
|
||||
export function createCliModeTools(library: string) {
|
||||
return {
|
||||
info: createInfoTool(library),
|
||||
retrieve: createRetrieveTool(library)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { z } from 'zod';
|
||||
|
||||
export function createInfoTool(defaultLibrary: string) {
|
||||
return tool({
|
||||
description: '调用 info API,基于 library 获取该图表库相关信息与要求文档。',
|
||||
description: '获取图表库的核心约束文档(使用规则、禁止写法、常见错误)。首轮必须调用。',
|
||||
inputSchema: z.object({
|
||||
library: z
|
||||
.enum(['g2', 'g6'])
|
||||
@@ -13,9 +13,10 @@ export function createInfoTool(defaultLibrary: string) {
|
||||
}),
|
||||
execute: async ({ library = defaultLibrary }) => {
|
||||
console.log(`Loaded info for library: ${library}`);
|
||||
const skillInfo = info(library);
|
||||
return {
|
||||
library,
|
||||
content: info(library)
|
||||
content: skillInfo?.constraintsContent ?? skillInfo?.content ?? '',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,19 +8,18 @@ interface RetrieveToolResult {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
path: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function createRetrieveTool(defaultLibrary: string) {
|
||||
return tool({
|
||||
description: '通过 retrieve 召回最相关参考文档。',
|
||||
description: '通过 retrieve 召回最相关参考文档。结果首位自动包含核心约束(使用规则、禁止写法、常见错误)。',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('用户需求或检索关键词'),
|
||||
library: z
|
||||
.enum(['g2', 'g6'])
|
||||
.optional()
|
||||
.describe('图表库名称,例如 g2、g6。').optional(),
|
||||
.describe('图表库名称,例如 g2、g6。'),
|
||||
topk: z
|
||||
.number()
|
||||
.int()
|
||||
@@ -30,20 +29,20 @@ export function createRetrieveTool(defaultLibrary: string) {
|
||||
.describe('召回文档数量,默认 5')
|
||||
}),
|
||||
execute: async ({ query, library, topk }) => {
|
||||
const retrievedSkills = retrieve(query, library ?? defaultLibrary, topk ?? 5, true);
|
||||
const retrievedSkills = retrieve(query, {
|
||||
library: library ?? defaultLibrary,
|
||||
topK: topk ?? 5,
|
||||
content: true,
|
||||
includeInfo: true,
|
||||
});
|
||||
console.log(`Retrieved ${retrievedSkills.length} skills for query: "${query}"`);
|
||||
|
||||
const results: RetrieveToolResult[] = [];
|
||||
for (const skill of retrievedSkills) {
|
||||
results.push({
|
||||
return retrievedSkills.map((skill) => ({
|
||||
id: skill.id,
|
||||
title: skill.title,
|
||||
description: skill.description || '',
|
||||
path: skill.path,
|
||||
content: skill.content || '',
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,7 +156,62 @@ chart.options({
|
||||
|
||||
## 2. Common Mistakes / 常见错误
|
||||
|
||||
代码示例:
|
||||
### ⚠️ 最高频错误:禁止多次调用 `chart.options()`
|
||||
|
||||
`chart.options()` 是**全量替换**,不是合并。多次调用时**只有最后一次生效**,前面的配置全部丢失。**每个图表只能调用一次 `chart.options()`。**
|
||||
|
||||
```javascript
|
||||
// ❌ Wrong: 多次调用 chart.options() —— 每次完整替换前一次,只有最后一次生效
|
||||
chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } }); // ❌ 被覆盖,不渲染
|
||||
chart.options({ type: 'line', data, encode: { x: 'x', y: 'y' } }); // ❌ 被覆盖,不渲染
|
||||
chart.options({ type: 'text', data, encode: { x: 'x', y: 'y', text: 'label' } }); // 只有这个生效
|
||||
|
||||
// ✅ Correct: 多 mark 叠加必须用 type: 'view' + children,一次 chart.options() 搞定
|
||||
chart.options({
|
||||
type: 'view',
|
||||
data,
|
||||
children: [
|
||||
{ type: 'interval', encode: { x: 'x', y: 'y' } },
|
||||
{ type: 'line', encode: { x: 'x', y: 'y' } },
|
||||
{ type: 'text', encode: { x: 'x', y: 'y', text: 'label' } },
|
||||
],
|
||||
});
|
||||
|
||||
// ✅ 子 mark 需要不同数据时,在 children 里单独指定 data
|
||||
chart.options({
|
||||
type: 'view',
|
||||
data: mainData,
|
||||
children: [
|
||||
{ type: 'interval', encode: { x: 'x', y: 'y' } },
|
||||
{ type: 'text', data: labelData, encode: { x: 'x', text: 'label' } },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
多 mark 组合规则:
|
||||
- 只能使用 `children`,禁止 `marks`、`layers` 等属性
|
||||
- `children` 不能嵌套(`children` 内不能再有 `type: 'view'` + `children`)
|
||||
- 复杂多坐标系组合用 `spaceLayer`/`spaceFlex`
|
||||
|
||||
```javascript
|
||||
// ❌ Wrong: 使用 marks/layers(禁止)
|
||||
chart.options({ type: 'view', data, marks: [...] }); // ❌
|
||||
chart.options({ type: 'view', data, Layers: [...] }); // ❌
|
||||
|
||||
// ❌ Wrong: children 嵌套(禁止)
|
||||
chart.options({ type: 'view', children: [{ type: 'view', children: [...] }] }); // ❌
|
||||
|
||||
// ✅ Correct: 复杂多坐标系组合用 spaceLayer
|
||||
chart.options({
|
||||
type: 'spaceLayer',
|
||||
children: [
|
||||
{ type: 'view', children: [...] },
|
||||
{ type: 'line', encode: { x: 'x', y: 'y' } },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### 其他常见错误
|
||||
|
||||
```javascript
|
||||
// ❌ Wrong: padding 数组形式(CSS 简写),G2 v5 不支持,会被忽略
|
||||
@@ -168,9 +223,6 @@ const chart = new Chart({ container: 'container', padding: 40 });
|
||||
// ✅ Correct: 分方向控制
|
||||
const chart = new Chart({ container: 'container', paddingTop: 40, paddingLeft: 60 });
|
||||
|
||||
// ✅ Correct: 默认 'auto' 即可(大多数场景无需配置)
|
||||
const chart = new Chart({ container: 'container', autoFit: true, height: 400 });
|
||||
|
||||
// ❌ Wrong: missing container
|
||||
const chart = new Chart({ width: 640, height: 480 });
|
||||
|
||||
@@ -189,97 +241,15 @@ chart.options({ label: { text: 'value' } });
|
||||
// ✅ Correct: labels (plural)
|
||||
chart.options({ labels: [{ text: 'value' }] });
|
||||
|
||||
// ❌ Wrong: 多次调用 chart.options() —— 每次调用完整替换前一次,只有最后一次生效
|
||||
chart.options({ type: 'interval', data, encode: { x: 'x', y: 'y' } }); // ❌ 被覆盖,不渲染
|
||||
chart.options({ type: 'line', data, encode: { x: 'x', y: 'y' } }); // ❌ 被覆盖,不渲染
|
||||
chart.options({ type: 'text', data, encode: { x: 'x', y: 'y', text: 'label' } }); // 只有这个生效
|
||||
|
||||
// ✅ Correct: 多 mark 叠加必须用 type: 'view' + children
|
||||
chart.options({
|
||||
type: 'view',
|
||||
data, // 共享数据(子 mark 可以覆盖)
|
||||
children: [
|
||||
{ type: 'interval', encode: { x: 'x', y: 'y' } },
|
||||
{ type: 'line', encode: { x: 'x', y: 'y' } },
|
||||
{ type: 'text', encode: { x: 'x', y: 'y', text: 'label' } },
|
||||
],
|
||||
});
|
||||
|
||||
// ✅ 子 mark 需要不同数据时,在 children 里单独指定 data
|
||||
chart.options({
|
||||
type: 'view',
|
||||
data: mainData,
|
||||
children: [
|
||||
{ type: 'interval', encode: { x: 'x', y: 'y' } }, // 用父级 mainData
|
||||
{ type: 'text', data: labelData, encode: { x: 'x', text: 'label' } }, // 用独立数据
|
||||
],
|
||||
});
|
||||
|
||||
// ⚠️ 多 mark 组合规则:
|
||||
// 1. 只能使用 children,禁止使用 marks、layers 等配置
|
||||
// 2. children 不能嵌套(children 内不能再有 children)
|
||||
// 3. 复杂组合使用 spaceLayer/spaceFlex
|
||||
|
||||
// ❌ Wrong: 使用 marks(禁止)
|
||||
chart.options({
|
||||
type: 'view',
|
||||
data,
|
||||
marks: [...], // ❌ 禁止!
|
||||
});
|
||||
|
||||
// ❌ Wrong: 使用 layers(禁止)
|
||||
chart.options({
|
||||
type: 'view',
|
||||
data,
|
||||
Layers: [...], // ❌ 禁止!
|
||||
});
|
||||
|
||||
// ✅ Correct: 使用 children
|
||||
chart.options({
|
||||
type: 'view',
|
||||
data,
|
||||
children: [ // ✅ 正确
|
||||
{ type: 'line', encode: { x: 'year', y: 'value' } },
|
||||
{ type: 'point', encode: { x: 'year', y: 'value' } },
|
||||
],
|
||||
});
|
||||
|
||||
// ❌ Wrong: children 嵌套(禁止)
|
||||
chart.options({
|
||||
type: 'view',
|
||||
children: [
|
||||
{
|
||||
type: 'view',
|
||||
children: [...], // ❌ 禁止!children 不能嵌套
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// ✅ Correct: 使用 spaceLayer/spaceFlex 处理复杂组合
|
||||
chart.options({
|
||||
type: 'spaceLayer',
|
||||
children: [
|
||||
{ type: 'view', children: [...] }, // ✅ spaceLayer 下可以有 view + children
|
||||
{ type: 'line', ... },
|
||||
],
|
||||
});
|
||||
|
||||
// ❌ Wrong: unnecessary scale type specification
|
||||
chart.options({
|
||||
scale: {
|
||||
x: { type: 'linear' }, // ❌ 不需要,默认就是 linear
|
||||
y: { type: 'linear' }, // ❌ 不需要
|
||||
},
|
||||
});
|
||||
chart.options({ scale: { x: { type: 'linear' }, y: { type: 'linear' } } });
|
||||
|
||||
// ✅ Correct: let G2 infer scale type automatically
|
||||
chart.options({
|
||||
scale: {
|
||||
y: { domain: [0, 100] }, // ✅ 只配置需要的属性
|
||||
},
|
||||
});
|
||||
chart.options({ scale: { y: { domain: [0, 100] } } });
|
||||
```
|
||||
|
||||
<!-- CONSTRAINTS:END -->
|
||||
|
||||
---
|
||||
|
||||
## 3. Basic Structure / 基础结构
|
||||
|
||||
@@ -7,7 +7,7 @@ export function registerRetrieveCommand(program: Command): void {
|
||||
.description('Search for skills matching a query')
|
||||
.option('--library <lib>', 'Filter by library (g2 or g6)')
|
||||
.option('--topk <n>', 'Number of results to return', '7')
|
||||
.option('--content', 'Include markdown content body')
|
||||
.option('--content', 'Include markdown content of matched reference docs (SKILL.md constraints are always prepended)')
|
||||
.option('--output <format>', 'Output format: json | text', 'text')
|
||||
.action(
|
||||
(
|
||||
@@ -15,10 +15,13 @@ export function registerRetrieveCommand(program: Command): void {
|
||||
opts: { library?: string; topk: string; content?: true; output: string }
|
||||
) => {
|
||||
const topK = parseInt(opts.topk, 10) || 7;
|
||||
const withContent = !!opts.content;
|
||||
|
||||
const skills = retrieve(query, {
|
||||
library: opts.library,
|
||||
topK,
|
||||
content: !!opts.content
|
||||
content: withContent,
|
||||
includeInfo: withContent,
|
||||
});
|
||||
|
||||
if (opts.output === 'json') {
|
||||
@@ -26,13 +29,26 @@ export function registerRetrieveCommand(program: Command): void {
|
||||
return;
|
||||
}
|
||||
|
||||
if (skills.length === 0) {
|
||||
console.log('No skills found.');
|
||||
const refSkills = skills.filter((s) => !s.id.startsWith('__info__'));
|
||||
const infoSkills = skills.filter((s) => s.id.startsWith('__info__'));
|
||||
|
||||
if (infoSkills.length > 0) {
|
||||
for (const infoSkill of infoSkills) {
|
||||
console.log(`${'═'.repeat(60)}`);
|
||||
console.log(` SKILL CONSTRAINTS: ${infoSkill.title}`);
|
||||
console.log(`${'═'.repeat(60)}`);
|
||||
if (infoSkill.content) console.log(infoSkill.content);
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
if (refSkills.length === 0) {
|
||||
console.log('No reference documents found.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Total ${skills.length} documents found:`);
|
||||
for (const [i, skill] of skills.entries()) {
|
||||
console.log(`Total ${refSkills.length} documents found:`);
|
||||
for (const [i, skill] of refSkills.entries()) {
|
||||
console.log(`\n${'─'.repeat(50)}`);
|
||||
console.log(`[${i + 1}] ${skill.title} (${skill.id})`);
|
||||
console.log(
|
||||
|
||||
+26
-2
@@ -69,7 +69,7 @@ export function retrieve(
|
||||
query: string,
|
||||
options: RetrieveOptions = {}
|
||||
): Skill[] {
|
||||
const { library, topK = 7, content = false } = options;
|
||||
const { library, topK = 7, content = false, includeInfo = content } = options;
|
||||
|
||||
let skills: Skill[];
|
||||
if (library) {
|
||||
@@ -85,7 +85,31 @@ export function retrieve(
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
return skills.map(({ content, ...skill }) => skill);
|
||||
skills = skills.map(({ content, ...skill }) => skill);
|
||||
}
|
||||
|
||||
if (includeInfo) {
|
||||
const libs = library ? [library] : [...new Set(skills.map((s) => s.library))];
|
||||
const infoSkills: Skill[] = libs.flatMap((lib) => {
|
||||
const skillInfo = getSkillInfo(lib);
|
||||
if (!skillInfo) return [];
|
||||
return [{
|
||||
id: `__info__${lib}`,
|
||||
title: skillInfo.name,
|
||||
description: skillInfo.description,
|
||||
library: lib,
|
||||
version: '',
|
||||
category: '__info__',
|
||||
subcategory: '',
|
||||
tags: [],
|
||||
difficulty: '',
|
||||
use_cases: [],
|
||||
anti_patterns: [],
|
||||
related: [],
|
||||
content: skillInfo.constraintsContent,
|
||||
}];
|
||||
});
|
||||
skills = [...infoSkills, ...skills];
|
||||
}
|
||||
|
||||
return skills;
|
||||
|
||||
@@ -27,6 +27,12 @@ export interface RetrieveOptions {
|
||||
library?: string;
|
||||
topK?: number;
|
||||
content?: boolean;
|
||||
/**
|
||||
* When true, prepend the library's SKILL.md core constraints as the first
|
||||
* result. Callers should set this whenever `content` is true so the model
|
||||
* always receives constraints alongside reference docs.
|
||||
*/
|
||||
includeInfo?: boolean;
|
||||
}
|
||||
|
||||
export interface ListOptions {
|
||||
@@ -39,7 +45,15 @@ export interface ListOptions {
|
||||
export interface SkillInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
/** Full SKILL.md body (after frontmatter). */
|
||||
content: string;
|
||||
/**
|
||||
* Content up to and including the `<!-- CONSTRAINTS:END -->` marker.
|
||||
* Used by `retrieve --content` to inject only the core constraints section
|
||||
* instead of the full document, avoiding context-window bloat.
|
||||
* Falls back to `content` when the marker is absent.
|
||||
*/
|
||||
constraintsContent: string;
|
||||
}
|
||||
|
||||
export interface BM25Options {
|
||||
|
||||
@@ -86,10 +86,18 @@ function build(): void {
|
||||
if (fs.existsSync(skillMd)) {
|
||||
const parsed = matter(fs.readFileSync(skillMd, 'utf-8'));
|
||||
const meta = parsed.data as Record<string, any>;
|
||||
const fullContent = parsed.content;
|
||||
const marker = '<!-- CONSTRAINTS:END -->';
|
||||
const markerIdx = fullContent.indexOf(marker);
|
||||
const constraintsContent =
|
||||
markerIdx !== -1
|
||||
? fullContent.slice(0, markerIdx + marker.length)
|
||||
: fullContent;
|
||||
info = {
|
||||
name: meta.name || libPath,
|
||||
description: (meta.description || '').replace(/\n\s*/g, ' ').trim(),
|
||||
content: parsed.content,
|
||||
content: fullContent,
|
||||
constraintsContent,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user