Install any skill in seconds. Free to start, no credit card required.
Get Started Free →專責處理 IDF (Information Display Frame) 類型的需求。讀取規格目錄結構,生成/審查 Query Side 設計與實作。支援 Java、TypeScript、Go 多語言。
.claude/skills/majiayu000-query-sub-agent/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 28% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 124% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 87% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 40% | 0% |
本 Skill 讀取以下規格檔案:
docs/specs/{feature-name}/
├── frame.yaml # 讀取 frame_concerns
├── requirements/ # 讀取查詢需求
│ └── req-{n}-{feature}.yaml
├── machine/ # 讀取 Query 規格
│ ├── query.yaml # Query Handler 規格
│ └── read-model.yaml # Read Model 規格
└── cross-context/ # 若需跨 BC 查詢
└── {context}.yamlyaml# docs/specs/{feature-name}/machine/query.yaml query: name: "{FeatureName}Query" type: "single" # | list | paginated | aggregated # Input 定義 input: name: "{FeatureName}QueryInput" fields: - name: "id" type: "string" required: true # 分頁參數 (若 type=paginated) - name: "page" type: "number" default: 1 - name: "pageSize" type: "number" default: 20 # Output 定義 output: name: "{FeatureName}QueryOutput" type: "single" # | list | paginated fields: - name: "id" type: "string" - name: "name" type: "string" # 分頁輸出 (若 type=paginated) pagination: total: "number" page: "number" pageSize: "number" hasNext: "boolean" # 快取策略 caching: enabled: true ttl: "5m" key_pattern: "{feature}:{id}" invalidation: - on_event: "{AggregateUpdatedEvent}" # 效能約束 performance: max_response_time: "100ms" max_items_per_page: 100
saga-orchestrator → runSubagent → query-sub-agent
├── 讀取規格目錄
├── 套用 coding-standards
└── 輸出 Query Side 代碼yamltask: type: "query" spec_dir: "docs/specs/get-workflow/" language: "typescript" output_paths: queries: "src/application/queries/" read_models: "src/infrastructure/read-models/"
typescript// src/application/queries/GetWorkflowByIdQuery.ts // Generated from: docs/specs/get-workflow/machine/query.yaml import { WorkflowReadModel } from '@/infrastructure/read-models/WorkflowReadModel'; import { CacheService } from '@/infrastructure/cache/CacheService'; // ===== Input/Output (from query.yaml) ===== export interface GetWorkflowByIdInput { readonly workflowId: string; } export interface GetWorkflowByIdOutput { readonly id: string; readonly boardId: string; readonly name: string; readonly stages: readonly StageView[]; readonly status: string; readonly createdAt: Date; } // ===== Query Handler ===== export class GetWorkflowByIdQuery { constructor( private readonly readModel: WorkflowReadModel, private readonly cache: CacheService, ) {} async execute(input: GetWorkflowByIdInput): Promise<GetWorkflowByIdOutput | null> { // ===== Pre-conditions ===== if (!input.workflowId) { throw new ValidationError('workflowId is required'); } // ===== Caching (from query.yaml#caching) ===== const cacheKey = `workflow:${input.workflowId}`; const cached = await this.cache.get<GetWorkflowByIdOutput>(cacheKey); if (cached) { return cached; } // ===== Query Read Model ===== const result = await this.readModel.findById(input.workflowId); if (result) { // Cache for 5 minutes (from query.yaml#caching.ttl) await this.cache.set(cacheKey, result, { ttl: 300 }); } return result; } }
typescript// src/application/queries/ListWorkflowsQuery.ts export interface ListWorkflowsInput { readonly boardId: string; readonly page?: number; readonly pageSize?: number; } export interface ListWorkflowsOutput { readonly items: readonly WorkflowSummary[]; readonly pagination: { readonly total: number; readonly page: number; readonly pageSize: number; readonly hasNext: boolean; }; } export class ListWorkflowsQuery { constructor( private readonly readModel: WorkflowReadModel, ) {} async execute(input: ListWorkflowsInput): Promise<ListWorkflowsOutput> { const page = input.page ?? 1; const pageSize = Math.min(input.pageSize ?? 20, 100); // Max 100 items const offset = (page - 1) * pageSize; const [items, total] = await Promise.all([ this.readModel.findByBoardId(input.boardId, { offset, limit: pageSize }), this.readModel.countByBoardId(input.boardId), ]); return { items, pagination: { total, page, pageSize, hasNext: offset + items.length < total, }, }; } }
typescript// src/infrastructure/read-models/WorkflowReadModel.ts export interface WorkflowReadModel { findById(id: string): Promise<WorkflowView | null>; findByBoardId(boardId: string, options: PaginationOptions): Promise<WorkflowSummary[]>; countByBoardId(boardId: string): Promise<number>; } // Implementation with optimized queries export class PostgresWorkflowReadModel implements WorkflowReadModel { constructor(private readonly db: Database) {} async findById(id: string): Promise<WorkflowView | null> { // Optimized query with joins for stages const result = await this.db.query(` SELECT w.*, json_agg(s.*) as stages FROM workflows w LEFT JOIN stages s ON s.workflow_id = w.id WHERE w.id = $1 GROUP BY w.id `, [id]); return result.rows[0] ?? null; } async findByBoardId( boardId: string, options: PaginationOptions ): Promise<WorkflowSummary[]> { // Summary query without heavy joins const result = await this.db.query(` SELECT id, name, status, created_at, (SELECT COUNT(*) FROM stages WHERE workflow_id = w.id) as stage_count FROM workflows w WHERE board_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3 `, [boardId, options.limit, options.offset]); return result.rows; } }
go// src/application/query/get_workflow_by_id.go package query import ( "context" "time" "myapp/infrastructure/cache" "myapp/infrastructure/readmodel" ) type GetWorkflowByIdInput struct { WorkflowID string `json:"workflow_id" validate:"required,uuid"` } type GetWorkflowByIdOutput struct { ID string `json:"id"` BoardID string `json:"board_id"` Name string `json:"name"` Stages []StageView `json:"stages"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` } type GetWorkflowByIdQuery struct { readModel readmodel.WorkflowReadModel cache cache.CacheService } func NewGetWorkflowByIdQuery( rm readmodel.WorkflowReadModel, c cache.CacheService, ) *GetWorkflowByIdQuery { return &GetWorkflowByIdQuery{readModel: rm, cache: c} } func (q *GetWorkflowByIdQuery) Execute( ctx context.Context, input GetWorkflowByIdInput, ) (*GetWorkflowByIdOutput, error) { // ===== Pre-conditions ===== if err := validate.Struct(input); err != nil { return nil, err } // ===== Caching ===== cacheKey := fmt.Sprintf("workflow:%s", input.WorkflowID) if cached, err := q.cache.Get(ctx, cacheKey); err == nil && cached != nil { return cached.(*GetWorkflowByIdOutput), nil } // ===== Query Read Model ===== result, err := q.readModel.FindByID(ctx, input.WorkflowID) if err != nil { return nil, err } if result != nil { // Cache for 5 minutes _ = q.cache.Set(ctx, cacheKey, result, 5*time.Minute) } return result, nil }
當 Domain Event 發生時,自動失效相關快取:
typescript// src/infrastructure/cache/WorkflowCacheInvalidator.ts export class WorkflowCacheInvalidator { constructor(private readonly cache: CacheService) {} @OnEvent('WorkflowCreatedEvent') @OnEvent('WorkflowUpdatedEvent') async invalidate(event: WorkflowEvent): void { // Invalidate single item cache await this.cache.delete(`workflow:${event.workflowId}`); // Invalidate list cache for the board await this.cache.deletePattern(`workflows:board:${event.boardId}:*`); } }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 23,788 | 16,226 | -32% | 1 | 1 | 0% | 4,257 | 4,913 | +15% | 0 | 0 | — |
case-02 | fail→pass | 25,425 | 26,519 | +4% | 1 | 1 | 0% | 5,484 | 7,003 | +28% | 0 | 0 | — |
case-03 | fail→pass | 32,764 | 18,570 | -43% | 1 | 1 | 0% | 5,558 | 6,646 | +20% | 0 | 0 | — |
case-04 | pass→pass | 22,776 | 24,005 | +5% | 1 | 1 | 0% | 3,747 | 7,028 | +88% | 0 | 0 | — |
case-05 | pass→pass | 19,919 | 26,862 | +35% | 1 | 1 | 0% | 4,062 | 7,251 | +79% | 0 | 0 | — |
case-06 | pass→pass | 24,786 | 25,102 | +1% | 1 | 1 | 0% | 3,870 | 7,983 | +106% | 0 | 0 | — |
case-07 | pass→pass | 18,867 | 17,993 | -5% | 1 | 1 | 0% | 3,618 | 5,535 | +53% | 0 | 0 | — |
case-08 | fail→pass | 26,129 | 14,723 | -44% | 1 | 1 | 0% | 2,496 | 5,588 | +124% | 0 | 0 | — |
case-09 | pass→pass | 18,140 | 16,601 | -8% | 1 | 1 | 0% | 2,521 | 5,027 | +99% | 0 | 0 | — |
case-10 | fail→fail | 25,568 | 14,375 | -44% | 1 | 1 | 0% | 2,281 | 4,529 | +99% | 0 | 0 | — |
case-11 | fail→fail | 19,620 | 22,782 | +16% | 1 | 1 | 0% | 2,549 | 6,364 | +150% | 0 | 0 | — |
case-12 | pass→pass | 13,528 | 31,410 | +132% | 1 | 1 | 0% | 2,601 | 5,315 | +104% | 0 | 0 | — |
case-13 | pass→pass | 9,184 | 7,439 | -19% | 1 | 1 | 0% | 624 | 3,062 | +391% | 0 | 0 | — |
case-14 | fail→pass | 20,452 | 18,133 | -11% | 1 | 1 | 0% | 3,020 | 5,642 | +87% | 0 | 0 | — |
case-15 | pass→pass | 18,878 | 24,564 | +30% | 1 | 1 | 0% | 2,699 | 6,430 | +138% | 0 | 0 | — |
case-16 | fail→fail | 23,722 | 18,642 | -21% | 1 | 1 | 0% | 3,296 | 5,397 | +64% | 0 | 0 | — |
case-17 | fail→pass | 18,874 | 11,927 | -37% | 1 | 1 | 0% | 2,845 | 3,992 | +40% | 0 | 0 | — |
case-18 | pass→pass | 15,009 | 9,525 | -37% | 1 | 1 | 0% | 1,717 | 3,531 | +106% | 0 | 0 | — |
case-19 | pass→pass | 18,945 | 15,378 | -19% | 1 | 1 | 0% | 2,287 | 4,057 | +77% | 0 | 0 | — |
case-20 | pass→pass | 22,848 | 18,496 | -19% | 1 | 1 | 0% | 3,302 | 5,206 | +58% | 0 | 0 | — |
case-21 | pass→pass | 14,580 | 23,160 | +59% | 1 | 1 | 0% | 2,647 | 5,244 | +98% | 0 | 0 | — |
case-22 | fail→pass | 46,116 | 9,859 | -79% | 1 | 1 | 0% | 1,165 | 3,503 | +201% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +27 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.