@yeyuan98/opencode-bioresearcher-plugin 1.3.1-alpha.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +14 -0
  2. package/dist/index.js +4 -1
  3. package/dist/misc-tools/index.d.ts +3 -0
  4. package/dist/misc-tools/index.js +3 -0
  5. package/dist/misc-tools/json-extract.d.ts +13 -0
  6. package/dist/misc-tools/json-extract.js +394 -0
  7. package/dist/misc-tools/json-infer.d.ts +13 -0
  8. package/dist/misc-tools/json-infer.js +199 -0
  9. package/dist/misc-tools/json-tools.d.ts +33 -0
  10. package/dist/misc-tools/json-tools.js +187 -0
  11. package/dist/misc-tools/json-validate.d.ts +13 -0
  12. package/dist/misc-tools/json-validate.js +228 -0
  13. package/dist/skills/bioresearcher-core/README.md +210 -0
  14. package/dist/skills/bioresearcher-core/SKILL.md +128 -0
  15. package/dist/skills/bioresearcher-core/examples/contexts.json +29 -0
  16. package/dist/skills/bioresearcher-core/examples/data-exchange-example.md +303 -0
  17. package/dist/skills/bioresearcher-core/examples/template.md +49 -0
  18. package/dist/skills/bioresearcher-core/patterns/calculator.md +215 -0
  19. package/dist/skills/bioresearcher-core/patterns/data-exchange.md +406 -0
  20. package/dist/skills/bioresearcher-core/patterns/json-tools.md +263 -0
  21. package/dist/skills/bioresearcher-core/patterns/progress.md +127 -0
  22. package/dist/skills/bioresearcher-core/patterns/retry.md +110 -0
  23. package/dist/skills/bioresearcher-core/patterns/shell-commands.md +79 -0
  24. package/dist/skills/bioresearcher-core/patterns/subagent-waves.md +186 -0
  25. package/dist/skills/bioresearcher-core/patterns/table-tools.md +260 -0
  26. package/dist/skills/bioresearcher-core/patterns/user-confirmation.md +187 -0
  27. package/dist/skills/bioresearcher-core/python/template.md +273 -0
  28. package/dist/skills/bioresearcher-core/python/template.py +323 -0
  29. package/dist/skills/long-table-summary/SKILL.md +437 -0
  30. package/dist/skills/long-table-summary/combine_outputs.py +336 -0
  31. package/dist/skills/long-table-summary/generate_prompts.py +211 -0
  32. package/dist/skills/long-table-summary/pyproject.toml +8 -0
  33. package/dist/skills/pubmed-weekly/SKILL.md +329 -329
  34. package/dist/skills/pubmed-weekly/pubmed_weekly.py +411 -411
  35. package/dist/skills/pubmed-weekly/pyproject.toml +8 -8
  36. package/package.json +7 -2
@@ -0,0 +1,33 @@
1
+ import { ToolContext } from '@opencode-ai/plugin/tool';
2
+ export declare const jsonValidate: {
3
+ description: string;
4
+ args: {
5
+ data: any;
6
+ schema: any;
7
+ version: any;
8
+ };
9
+ execute(args: {
10
+ [x: string]: any;
11
+ }, context: ToolContext): Promise<string>;
12
+ };
13
+ export declare const jsonExtract: {
14
+ description: string;
15
+ args: {
16
+ content: any;
17
+ path: any;
18
+ };
19
+ execute(args: {
20
+ [x: string]: any;
21
+ }, context: ToolContext): Promise<string>;
22
+ };
23
+ export declare const jsonInferSchema: {
24
+ description: string;
25
+ args: {
26
+ data: any;
27
+ title: any;
28
+ strict: any;
29
+ };
30
+ execute(args: {
31
+ [x: string]: any;
32
+ }, context: ToolContext): Promise<string>;
33
+ };
@@ -0,0 +1,187 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ const zod = require('zod');
3
+ function getFromJSONSchema() {
4
+ if (zod.fromJSONSchema) {
5
+ return zod.fromJSONSchema;
6
+ }
7
+ const v4classic = require('zod/v4/classic/external.js');
8
+ if (v4classic.fromJSONSchema) {
9
+ return v4classic.fromJSONSchema;
10
+ }
11
+ throw new Error('fromJSONSchema not available in zod package');
12
+ }
13
+ const fromJSONSchema = getFromJSONSchema();
14
+ export const jsonValidate = tool({
15
+ description: `Validate JSON data against a JSON Schema.
16
+ Returns validation result with detailed error paths and messages.
17
+ Supports JSON Schema draft-04, draft-07, draft-2020-12, and OpenAPI 3.0.`,
18
+ args: {
19
+ data: z.any().describe("JSON data to validate (object, array, or primitive)"),
20
+ schema: z.any().describe("JSON Schema object to validate against"),
21
+ version: z.enum(["draft-04", "draft-07", "draft-2020-12", "openapi-3.0"])
22
+ .optional()
23
+ .default("draft-2020-12")
24
+ .describe("JSON Schema version (default: draft-2020-12)")
25
+ },
26
+ execute: async (args) => {
27
+ try {
28
+ const zodSchema = z.fromJSONSchema(args.schema, {
29
+ defaultTarget: args.version
30
+ });
31
+ const result = zodSchema.safeParse(args.data);
32
+ if (result.success) {
33
+ return JSON.stringify({
34
+ valid: true,
35
+ data: result.data
36
+ }, null, 2);
37
+ }
38
+ else {
39
+ const errors = result.error.issues.map((issue) => ({
40
+ path: issue.path.join('.') || '(root)',
41
+ message: issue.message,
42
+ code: issue.code
43
+ }));
44
+ return JSON.stringify({
45
+ valid: false,
46
+ errors,
47
+ errorCount: errors.length
48
+ }, null, 2);
49
+ }
50
+ }
51
+ catch (e) {
52
+ return JSON.stringify({
53
+ valid: false,
54
+ schemaError: e instanceof Error ? e.message : 'Unknown schema error'
55
+ }, null, 2);
56
+ }
57
+ }
58
+ });
59
+ export const jsonExtract = tool({
60
+ description: `Extract JSON from markdown or text content.
61
+ Handles code blocks (\`\`\`json) and raw {...} or [...] patterns.
62
+ Returns extracted JSON or error with details.`,
63
+ args: {
64
+ content: z.string().describe("Markdown or text content containing JSON"),
65
+ path: z.string().optional()
66
+ .describe("JSONPath to extract nested value (e.g., 'data.items.0')")
67
+ },
68
+ execute: async (args) => {
69
+ let jsonStr = null;
70
+ const codeBlockMatch = args.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
71
+ if (codeBlockMatch) {
72
+ jsonStr = codeBlockMatch[1].trim();
73
+ }
74
+ if (!jsonStr) {
75
+ const objStart = args.content.indexOf('{');
76
+ const arrStart = args.content.indexOf('[');
77
+ if (objStart !== -1 || arrStart !== -1) {
78
+ const startChar = objStart !== -1 && (arrStart === -1 || objStart < arrStart) ? '{' : '[';
79
+ const endChar = startChar === '{' ? '}' : ']';
80
+ const startIndex = args.content.indexOf(startChar);
81
+ let depth = 0;
82
+ let endIndex = -1;
83
+ for (let i = startIndex; i < args.content.length; i++) {
84
+ if (args.content[i] === startChar)
85
+ depth++;
86
+ if (args.content[i] === endChar)
87
+ depth--;
88
+ if (depth === 0) {
89
+ endIndex = i + 1;
90
+ break;
91
+ }
92
+ }
93
+ if (endIndex !== -1) {
94
+ jsonStr = args.content.slice(startIndex, endIndex);
95
+ }
96
+ }
97
+ }
98
+ if (!jsonStr) {
99
+ return JSON.stringify({
100
+ success: false,
101
+ error: "No JSON found in content"
102
+ }, null, 2);
103
+ }
104
+ try {
105
+ let data = JSON.parse(jsonStr);
106
+ if (args.path) {
107
+ const parts = args.path.split('.');
108
+ for (const part of parts) {
109
+ if (data === null || data === undefined) {
110
+ return JSON.stringify({
111
+ success: false,
112
+ error: `Path '${args.path}' not found (null/undefined at '${part}')`
113
+ }, null, 2);
114
+ }
115
+ data = data[part];
116
+ }
117
+ }
118
+ return JSON.stringify({
119
+ success: true,
120
+ data
121
+ }, null, 2);
122
+ }
123
+ catch (e) {
124
+ return JSON.stringify({
125
+ success: false,
126
+ error: `Invalid JSON: ${e instanceof Error ? e.message : 'Unknown error'}`,
127
+ extractedText: jsonStr.length > 200 ? jsonStr.slice(0, 200) + '...' : jsonStr
128
+ }, null, 2);
129
+ }
130
+ }
131
+ });
132
+ export const jsonInferSchema = tool({
133
+ description: `Infer a JSON Schema from example JSON data.
134
+ Useful for creating schemas from sample data.`,
135
+ args: {
136
+ data: z.any().describe("Example JSON data to infer schema from"),
137
+ title: z.string().optional().describe("Optional schema title"),
138
+ strict: z.boolean().optional().default(false)
139
+ .describe("If true, set additionalProperties: false for objects")
140
+ },
141
+ execute: async (args) => {
142
+ function inferType(value, strict) {
143
+ if (value === null)
144
+ return { type: "null" };
145
+ if (typeof value === "string")
146
+ return { type: "string" };
147
+ if (typeof value === "boolean")
148
+ return { type: "boolean" };
149
+ if (typeof value === "number") {
150
+ return Number.isInteger(value) ? { type: "integer" } : { type: "number" };
151
+ }
152
+ if (Array.isArray(value)) {
153
+ if (value.length === 0) {
154
+ return { type: "array", items: {} };
155
+ }
156
+ return { type: "array", items: inferType(value[0], strict) };
157
+ }
158
+ if (typeof value === "object" && value !== null) {
159
+ const obj = value;
160
+ const properties = {};
161
+ const required = [];
162
+ for (const [key, val] of Object.entries(obj)) {
163
+ properties[key] = inferType(val, strict);
164
+ required.push(key);
165
+ }
166
+ const schema = {
167
+ type: "object",
168
+ properties,
169
+ required
170
+ };
171
+ if (strict) {
172
+ schema.additionalProperties = false;
173
+ }
174
+ return schema;
175
+ }
176
+ return {};
177
+ }
178
+ const schema = {
179
+ $schema: "https://json-schema.org/draft/2020-12/schema",
180
+ ...inferType(args.data, args.strict ?? false)
181
+ };
182
+ if (args.title) {
183
+ schema.title = args.title;
184
+ }
185
+ return JSON.stringify(schema, null, 2);
186
+ }
187
+ });
@@ -0,0 +1,13 @@
1
+ import { ToolContext } from '@opencode-ai/plugin/tool';
2
+ import { z } from 'zod';
3
+ export declare const jsonValidate: {
4
+ description: string;
5
+ args: {
6
+ data: z.ZodString;
7
+ schema: z.ZodString;
8
+ };
9
+ execute(args: {
10
+ data: string;
11
+ schema: string;
12
+ }, context: ToolContext): Promise<string>;
13
+ };
@@ -0,0 +1,228 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { z, fromJSONSchema } from 'zod';
3
+ import * as path from 'path';
4
+ const UNSUPPORTED_SCHEMA_FEATURES = [
5
+ 'not', 'unevaluatedItems', 'unevaluatedProperties',
6
+ 'if', 'then', 'else', 'dependentSchemas', 'dependentRequired'
7
+ ];
8
+ const SILENTLY_IGNORED_FEATURES = [
9
+ 'uniqueItems', 'contains', 'minContains', 'maxContains'
10
+ ];
11
+ function analyzeSchemaFeatures(schema, prefix = '') {
12
+ const result = { unsupported: [], ignored: [] };
13
+ if (typeof schema !== 'object' || schema === null)
14
+ return result;
15
+ const obj = schema;
16
+ for (const feature of UNSUPPORTED_SCHEMA_FEATURES) {
17
+ if (feature in obj) {
18
+ result.unsupported.push(prefix ? `${prefix}.${feature}` : feature);
19
+ }
20
+ }
21
+ for (const feature of SILENTLY_IGNORED_FEATURES) {
22
+ if (feature in obj) {
23
+ result.ignored.push(prefix ? `${prefix}.${feature}` : feature);
24
+ }
25
+ }
26
+ if (obj.properties && typeof obj.properties === 'object') {
27
+ for (const [key, value] of Object.entries(obj.properties)) {
28
+ const nested = analyzeSchemaFeatures(value, prefix ? `${prefix}.properties.${key}` : `properties.${key}`);
29
+ result.unsupported.push(...nested.unsupported);
30
+ result.ignored.push(...nested.ignored);
31
+ }
32
+ }
33
+ if (obj.items) {
34
+ const nested = analyzeSchemaFeatures(obj.items, prefix ? `${prefix}.items` : 'items');
35
+ result.unsupported.push(...nested.unsupported);
36
+ result.ignored.push(...nested.ignored);
37
+ }
38
+ return result;
39
+ }
40
+ function formatZodError(error) {
41
+ return error.issues.map(issue => {
42
+ const result = {
43
+ path: issue.path.join('.') || '(root)',
44
+ message: issue.message,
45
+ code: issue.code
46
+ };
47
+ if (issue.code === 'invalid_type') {
48
+ const typedIssue = issue;
49
+ result.expected = typeof typedIssue.expected === 'string'
50
+ ? 'string'
51
+ : typedIssue.expected === 'number'
52
+ ? 'number'
53
+ : String(typedIssue.expected);
54
+ result.received = typeof typedIssue.input === 'string'
55
+ ? 'string'
56
+ : String(typedIssue.input);
57
+ }
58
+ if (issue.code === 'too_small') {
59
+ const typedIssue = issue;
60
+ result.constraint = {
61
+ type: typedIssue.origin === 'string' ? 'minLength' : 'minimum',
62
+ value: String(typedIssue.minimum)
63
+ };
64
+ }
65
+ if (issue.code === 'too_big') {
66
+ const typedIssue = issue;
67
+ result.constraint = {
68
+ type: typedIssue.origin === 'string' ? 'maxLength' : 'maximum',
69
+ value: String(typedIssue.maximum)
70
+ };
71
+ }
72
+ if (issue.code === 'invalid_format') {
73
+ const formatIssue = issue;
74
+ result.constraint = {
75
+ type: 'format',
76
+ value: String(formatIssue.format)
77
+ };
78
+ }
79
+ return result;
80
+ });
81
+ }
82
+ export const jsonValidate = tool({
83
+ description: "Validate JSON data against a JSON Schema (supports Draft-4, Draft-7, Draft-2020-12, OpenAPI 3.0). Returns detailed validation results with warnings for unsupported/ignored schema features.",
84
+ args: {
85
+ data: z.string().describe("JSON data string to validate"),
86
+ schema: z.string().describe("JSON Schema string or file path (auto-reads from file if path detected)")
87
+ },
88
+ execute: async (args, context) => {
89
+ let parsedData;
90
+ let parsedSchema;
91
+ let schemaString = args.schema;
92
+ const isWindowsPath = /^[A-Za-z]:[/\\]/.test(args.schema);
93
+ const isUnixPath = /^\//.test(args.schema);
94
+ const isRelativePath = /^\.{1,2}(\/|\\)/.test(args.schema);
95
+ if (isWindowsPath || isUnixPath || isRelativePath) {
96
+ const resolvedPath = path.isAbsolute(args.schema)
97
+ ? args.schema
98
+ : path.join(context.directory, args.schema);
99
+ try {
100
+ const fs = await import('fs');
101
+ schemaString = fs.readFileSync(resolvedPath, 'utf-8');
102
+ }
103
+ catch (e) {
104
+ return JSON.stringify({
105
+ success: false,
106
+ error: {
107
+ code: 'INVALID_JSON_SCHEMA',
108
+ message: 'Failed to read schema from file',
109
+ details: e instanceof Error ? e.message : 'Unknown error',
110
+ hints: [
111
+ 'Check the file path for typos',
112
+ 'Ensure file exists and is accessible',
113
+ 'Use a relative path from the project directory'
114
+ ]
115
+ }
116
+ }, null, 2);
117
+ }
118
+ }
119
+ try {
120
+ parsedData = JSON.parse(args.data);
121
+ }
122
+ catch (e) {
123
+ return JSON.stringify({
124
+ success: false,
125
+ error: {
126
+ code: 'INVALID_JSON_DATA',
127
+ message: 'Data is not valid JSON',
128
+ details: e instanceof Error ? e.message : 'Parse error',
129
+ hints: [
130
+ 'Ensure data is valid JSON syntax',
131
+ 'Check for missing quotes, trailing commas, or unescaped characters'
132
+ ]
133
+ }
134
+ }, null, 2);
135
+ }
136
+ try {
137
+ parsedSchema = JSON.parse(schemaString);
138
+ }
139
+ catch (e) {
140
+ return JSON.stringify({
141
+ success: false,
142
+ error: {
143
+ code: 'INVALID_JSON_SCHEMA',
144
+ message: 'Schema is not valid JSON',
145
+ details: e instanceof Error ? e.message : 'Parse error',
146
+ hints: [
147
+ 'Ensure schema is valid JSON syntax',
148
+ 'Check JSON Schema syntax at https://json-schema.org'
149
+ ]
150
+ }
151
+ }, null, 2);
152
+ }
153
+ const analysis = analyzeSchemaFeatures(parsedSchema);
154
+ if (analysis.unsupported.length > 0) {
155
+ return JSON.stringify({
156
+ success: false,
157
+ error: {
158
+ code: 'UNSUPPORTED_SCHEMA_FEATURE',
159
+ message: 'Schema uses features not supported by Zod',
160
+ details: {
161
+ unsupportedFeatures: analysis.unsupported,
162
+ explanation: 'These JSON Schema features cannot be converted to Zod schemas'
163
+ },
164
+ hints: [
165
+ 'Remove unsupported features from schema',
166
+ `Unsupported features: ${analysis.unsupported.join(', ')}`,
167
+ 'Consider using a simpler schema structure'
168
+ ]
169
+ }
170
+ }, null, 2);
171
+ }
172
+ let zodSchema;
173
+ try {
174
+ zodSchema = fromJSONSchema(parsedSchema);
175
+ }
176
+ catch (e) {
177
+ return JSON.stringify({
178
+ success: false,
179
+ error: {
180
+ code: 'UNSUPPORTED_SCHEMA_FEATURE',
181
+ message: 'Failed to convert JSON Schema to validation schema',
182
+ details: e instanceof Error ? e.message : 'Conversion error',
183
+ hints: [
184
+ 'Schema may use unsupported features',
185
+ 'Check schema syntax and structure'
186
+ ]
187
+ }
188
+ }, null, 2);
189
+ }
190
+ const result = zodSchema.safeParse(parsedData);
191
+ if (result.success) {
192
+ const response = {
193
+ success: true,
194
+ data: result.data,
195
+ metadata: {
196
+ errorCount: 0,
197
+ schemaFeatures: {
198
+ validated: Object.keys(parsedSchema).filter(k => !SILENTLY_IGNORED_FEATURES.includes(k)),
199
+ ignored: analysis.ignored
200
+ }
201
+ }
202
+ };
203
+ if (analysis.ignored.length > 0) {
204
+ response.warnings = analysis.ignored.map((feature) => ({
205
+ code: 'IGNORED_SCHEMA_FEATURE',
206
+ message: `Schema feature '${feature}' is not validated`,
207
+ details: 'This feature is silently ignored by Zod - data may not match intended constraints'
208
+ }));
209
+ }
210
+ return JSON.stringify(response, null, 2);
211
+ }
212
+ else {
213
+ return JSON.stringify({
214
+ success: true,
215
+ data: null,
216
+ valid: false,
217
+ errors: formatZodError(result.error),
218
+ metadata: {
219
+ errorCount: result.error.issues.length,
220
+ schemaFeatures: {
221
+ validated: Object.keys(parsedSchema).filter(k => !SILENTLY_IGNORED_FEATURES.includes(k)),
222
+ ignored: analysis.ignored
223
+ }
224
+ }
225
+ }, null, 2);
226
+ }
227
+ }
228
+ });
@@ -0,0 +1,210 @@
1
+ # BioResearcher Core
2
+
3
+ Core library skill providing reusable patterns and utilities for BioResearcher skills.
4
+
5
+ ## Overview
6
+
7
+ BioResearcher Core provides:
8
+ - **Workflow patterns** for common operations (retry, progress, subagent waves)
9
+ - **Data handling patterns** for JSON and table operations
10
+ - **Python utilities** for template generation
11
+ - **Tool-first architecture** using existing plugin tools
12
+
13
+ ## Installation
14
+
15
+ No installation required. This skill uses only:
16
+ - Existing plugin tools (jsonExtract, jsonValidate, etc.)
17
+ - Python standard library
18
+
19
+ ## Quick Start
20
+
21
+ ### Load the Skill
22
+
23
+ ```
24
+ skill bioresearcher-core
25
+ ```
26
+
27
+ ### Extract Skill Path
28
+
29
+ From the `<skill_files>` section in the skill tool output, extract the `<skill_path>` value for use in commands.
30
+
31
+ ### Use a Pattern
32
+
33
+ ```
34
+ Read <skill_path>/patterns/retry.md
35
+ ```
36
+
37
+ ### Run Python Script
38
+
39
+ ```bash
40
+ uv run python <skill_path>/python/template.py generate-batches \
41
+ --template template.md \
42
+ --contexts contexts.json \
43
+ --output-dir ./outputs
44
+ ```
45
+
46
+ ## Available Patterns
47
+
48
+ ### Workflow Control
49
+
50
+ | Pattern | Description | File |
51
+ |---------|-------------|------|
52
+ | Retry | Retry with backoff using blockingTimer | `patterns/retry.md` |
53
+ | Progress | Track and report batch progress | `patterns/progress.md` |
54
+ | Subagent Waves | Parallel subagent processing | `patterns/subagent-waves.md` |
55
+ | Shell Commands | Cross-platform command generation | `patterns/shell-commands.md` |
56
+ | User Confirmation | Request user confirmation | `patterns/user-confirmation.md` |
57
+
58
+ ### Data Handling
59
+
60
+ | Pattern | Description | File |
61
+ |---------|-------------|------|
62
+ | JSON Tools | jsonExtract/jsonValidate/jsonInfer | `patterns/json-tools.md` |
63
+ | Table Tools | Combine outputs with table tools | `patterns/table-tools.md` |
64
+ | Data Exchange | Main/subagent communication | `patterns/data-exchange.md` |
65
+ | Calculator | In-workflow calculations | `patterns/calculator.md` |
66
+
67
+ ## Available Tools
68
+
69
+ These tools are always available without loading pattern files:
70
+
71
+ ### JSON Tools
72
+
73
+ | Tool | Description |
74
+ |------|-------------|
75
+ | `jsonExtract` | Extract JSON from files |
76
+ | `jsonValidate` | Validate JSON against schemas |
77
+ | `jsonInfer` | Infer schemas from data |
78
+
79
+ ### Utility Tools
80
+
81
+ | Tool | Description |
82
+ |------|-------------|
83
+ | `blockingTimer` | Blocking delays (0-300 seconds) |
84
+ | `calculator` | Mathematical expressions |
85
+
86
+ ### Table Tools
87
+
88
+ | Tool | Description |
89
+ |------|-------------|
90
+ | `tableCreateFile` | Create Excel/CSV from data |
91
+ | `tableAppendRows` | Append rows to tables |
92
+
93
+ ## Python Utilities
94
+
95
+ ### Template Engine
96
+
97
+ Location: `python/template.py`
98
+
99
+ Commands:
100
+ - `fill` - Generate single file from template
101
+ - `generate-batches` - Generate multiple files from template
102
+ - `escape` - Escape text for markdown
103
+
104
+ See `python/template.md` for full documentation.
105
+
106
+ ## Examples
107
+
108
+ ### Example Files
109
+
110
+ | File | Description |
111
+ |------|-------------|
112
+ | `examples/template.md` | Example template with placeholders |
113
+ | `examples/contexts.json` | Example contexts for batch generation |
114
+ | `examples/data-exchange-example.md` | Complete data exchange workflow |
115
+
116
+ ## Architecture
117
+
118
+ ### Tool-First Design
119
+
120
+ This skill maximizes use of existing plugin tools:
121
+
122
+ ```
123
+ ┌─────────────────────────────────────────────────┐
124
+ │ BioResearcher Core │
125
+ ├─────────────────────────────────────────────────┤
126
+ │ Patterns (markdown) │ Python Utilities │
127
+ │ - retry.md │ - template.py │
128
+ │ - progress.md │ │
129
+ │ - subagent-waves.md │ │
130
+ │ - json-tools.md │ │
131
+ │ - table-tools.md │ │
132
+ │ - data-exchange.md │ │
133
+ │ - calculator.md │ │
134
+ │ - shell-commands.md │ │
135
+ │ - user-confirmation.md │ │
136
+ ├─────────────────────────┴───────────────────────┤
137
+ │ Plugin Tools │
138
+ │ jsonExtract │ jsonValidate │ jsonInfer │
139
+ │ blockingTimer │ calculator │
140
+ │ tableCreateFile │ tableAppendRows │
141
+ └─────────────────────────────────────────────────┘
142
+ ```
143
+
144
+ ### Pattern Modules
145
+
146
+ Each pattern is a self-contained markdown file:
147
+ - Independent (no dependencies on other patterns)
148
+ - Self-documenting (explains itself)
149
+ - Minimal loading (agent reads only what it needs)
150
+
151
+ ## Integration with Other Skills
152
+
153
+ ### long-table-summary
154
+
155
+ BioResearcher Core can be used by long-table-summary:
156
+ - Use `jsonExtract` instead of manual JSON parsing
157
+ - Use `jsonValidate` for schema validation
158
+ - Apply retry/progress patterns
159
+ - Use template.py for prompt generation
160
+
161
+ ### pubmed-weekly
162
+
163
+ BioResearcher Core can be used by pubmed-weekly:
164
+ - Apply retry pattern for download failures
165
+ - Use shell-commands pattern for cross-platform support
166
+
167
+ ## Design Principles
168
+
169
+ 1. **Tool-first**: Use existing tools before creating Python scripts
170
+ 2. **Self-contained**: Each pattern is independent
171
+ 3. **Minimal dependencies**: Only Python standard library
172
+ 4. **Clear documentation**: Every file documents itself
173
+ 5. **Standard protocols**: JSON for data exchange
174
+
175
+ ## Directory Structure
176
+
177
+ ```
178
+ plugin/skills/bioresearcher-core/
179
+ ├── SKILL.md # Routing document
180
+ ├── README.md # This file
181
+ ├── patterns/ # Workflow patterns
182
+ │ ├── retry.md
183
+ │ ├── progress.md
184
+ │ ├── subagent-waves.md
185
+ │ ├── shell-commands.md
186
+ │ ├── user-confirmation.md
187
+ │ ├── json-tools.md
188
+ │ ├── table-tools.md
189
+ │ ├── data-exchange.md
190
+ │ └── calculator.md
191
+ ├── python/ # Python utilities
192
+ │ ├── template.py
193
+ │ └── template.md
194
+ └── examples/ # Example files
195
+ ├── template.md
196
+ ├── contexts.json
197
+ └── data-exchange-example.md
198
+ ```
199
+
200
+ ## Contributing
201
+
202
+ When adding new patterns:
203
+ 1. Create self-contained markdown file in `patterns/`
204
+ 2. Follow existing pattern structure
205
+ 3. Include examples and error handling
206
+ 4. Update SKILL.md with new pattern reference
207
+
208
+ ## License
209
+
210
+ Part of the BioResearcher plugin ecosystem.