@rotifer/playground 0.30.2 → 0.31.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 (59) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +6 -6
  3. package/README.zh.md +1 -1
  4. package/dist/arena/invalidation-criteria.js +8 -8
  5. package/dist/commands/agent-create.js +1 -1
  6. package/dist/commands/agent-create.js.map +1 -1
  7. package/dist/commands/arena-submit.d.ts.map +1 -1
  8. package/dist/commands/arena-submit.js +34 -10
  9. package/dist/commands/arena-submit.js.map +1 -1
  10. package/dist/commands/arena-watch.js +1 -1
  11. package/dist/commands/arena-watch.js.map +1 -1
  12. package/dist/commands/compile.d.ts.map +1 -1
  13. package/dist/commands/compile.js +15 -8
  14. package/dist/commands/compile.js.map +1 -1
  15. package/dist/commands/test.d.ts.map +1 -1
  16. package/dist/commands/test.js +3 -9
  17. package/dist/commands/test.js.map +1 -1
  18. package/dist/commands/vg.d.ts.map +1 -1
  19. package/dist/commands/vg.js +18 -1
  20. package/dist/commands/vg.js.map +1 -1
  21. package/dist/commands/wrap.d.ts +6 -0
  22. package/dist/commands/wrap.d.ts.map +1 -1
  23. package/dist/commands/wrap.js +12 -3
  24. package/dist/commands/wrap.js.map +1 -1
  25. package/dist/publish/pre-publish-check.d.ts.map +1 -1
  26. package/dist/publish/pre-publish-check.js +12 -0
  27. package/dist/publish/pre-publish-check.js.map +1 -1
  28. package/dist/scanner/index.d.ts +1 -1
  29. package/dist/scanner/index.d.ts.map +1 -1
  30. package/dist/scanner/index.js +2 -1
  31. package/dist/scanner/index.js.map +1 -1
  32. package/dist/scanner/rules.d.ts +1 -1
  33. package/dist/scanner/rules.d.ts.map +1 -1
  34. package/dist/scanner/rules.js +14 -1
  35. package/dist/scanner/rules.js.map +1 -1
  36. package/dist/scanner/scanner.d.ts +2 -0
  37. package/dist/scanner/scanner.d.ts.map +1 -1
  38. package/dist/scanner/scanner.js +60 -6
  39. package/dist/scanner/scanner.js.map +1 -1
  40. package/dist/scanner/types.d.ts +3 -0
  41. package/dist/scanner/types.d.ts.map +1 -1
  42. package/dist/testsuite/gate.d.ts +6 -3
  43. package/dist/testsuite/gate.d.ts.map +1 -1
  44. package/dist/testsuite/gate.js +15 -6
  45. package/dist/testsuite/gate.js.map +1 -1
  46. package/dist/utils/fidelity-discount.d.ts +24 -0
  47. package/dist/utils/fidelity-discount.d.ts.map +1 -1
  48. package/dist/utils/fidelity-discount.js +35 -0
  49. package/dist/utils/fidelity-discount.js.map +1 -1
  50. package/dist/utils/javy-compiler.d.ts +25 -0
  51. package/dist/utils/javy-compiler.d.ts.map +1 -1
  52. package/dist/utils/javy-compiler.js +84 -1
  53. package/dist/utils/javy-compiler.js.map +1 -1
  54. package/genes/gene-health-scanner/index.ts +476 -0
  55. package/genes/gene-health-scanner/phenotype.json +47 -0
  56. package/genes/genesis-web-search/index.ts +5 -3
  57. package/genes/genesis-web-search/phenotype.json +2 -2
  58. package/genes/genesis-web-search-lite/phenotype.json +2 -2
  59. package/package.json +5 -5
@@ -0,0 +1,476 @@
1
+ import { readdirSync, readFileSync, existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+
5
+ interface HealthInput {
6
+ genesDir?: string;
7
+ verbose?: boolean;
8
+ }
9
+
10
+ interface DimensionResult {
11
+ name: string;
12
+ score: number;
13
+ findings: string[];
14
+ suggestions: string[];
15
+ }
16
+
17
+ interface HealthOutput {
18
+ summary: string;
19
+ dimensions: DimensionResult[];
20
+ overallScore: number;
21
+ geneCount: number;
22
+ recommendations: string[];
23
+ }
24
+
25
+ interface Phenotype {
26
+ name?: string;
27
+ version?: string;
28
+ domain?: string;
29
+ fidelity?: string;
30
+ description?: string;
31
+ author?: string;
32
+ tags?: string[];
33
+ dependencies?: string[];
34
+ inputSchema?: { properties?: Record<string, unknown> };
35
+ outputSchema?: { properties?: Record<string, unknown> };
36
+ }
37
+
38
+ interface RunLogEntry {
39
+ success: boolean;
40
+ durationMs: number;
41
+ }
42
+
43
+ function loadPhenotypes(genesDir: string): Map<string, Phenotype> {
44
+ const result = new Map<string, Phenotype>();
45
+ if (!existsSync(genesDir)) return result;
46
+
47
+ for (const entry of readdirSync(genesDir, { withFileTypes: true })) {
48
+ if (!entry.isDirectory()) continue;
49
+ const phenoPath = join(genesDir, entry.name, "phenotype.json");
50
+ if (!existsSync(phenoPath)) continue;
51
+ try {
52
+ const raw = readFileSync(phenoPath, "utf-8");
53
+ result.set(entry.name, JSON.parse(raw) as Phenotype);
54
+ } catch {
55
+ result.set(entry.name, {});
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+
61
+ function hasImplementation(genesDir: string, geneName: string): boolean {
62
+ return (
63
+ existsSync(join(genesDir, geneName, "index.ts")) ||
64
+ existsSync(join(genesDir, geneName, "index.js"))
65
+ );
66
+ }
67
+
68
+ function readImplementation(genesDir: string, geneName: string): string | null {
69
+ for (const file of ["index.ts", "index.js"]) {
70
+ const p = join(genesDir, geneName, file);
71
+ if (existsSync(p)) {
72
+ try {
73
+ return readFileSync(p, "utf-8");
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+
82
+ function analyzeCapabilityDistribution(phenotypes: Map<string, Phenotype>): DimensionResult {
83
+ const domainCounts = new Map<string, number>();
84
+ const findings: string[] = [];
85
+ const suggestions: string[] = [];
86
+
87
+ for (const [, pheno] of phenotypes) {
88
+ const domain = pheno.domain ?? "unknown";
89
+ domainCounts.set(domain, (domainCounts.get(domain) ?? 0) + 1);
90
+ }
91
+
92
+ const total = phenotypes.size;
93
+ const domainCount = domainCounts.size;
94
+ findings.push(`${total} genes across ${domainCount} domains`);
95
+
96
+ let maxRatio = 0;
97
+ let dominantDomain = "";
98
+ for (const [domain, count] of domainCounts) {
99
+ const ratio = total > 0 ? count / total : 0;
100
+ findings.push(`${domain}: ${count} genes (${(ratio * 100).toFixed(1)}%)`);
101
+ if (ratio > maxRatio) {
102
+ maxRatio = ratio;
103
+ dominantDomain = domain;
104
+ }
105
+ }
106
+
107
+ let score = 1.0;
108
+ if (maxRatio > 0.6) {
109
+ score = Math.max(0, 1.0 - (maxRatio - 0.6) * 2.5);
110
+ suggestions.push(
111
+ `Domain "${dominantDomain}" holds ${(maxRatio * 100).toFixed(1)}% of genes — capability imbalance (偏科). Diversify into underrepresented domains.`
112
+ );
113
+ }
114
+ if (domainCount < 3 && total > 5) {
115
+ score = Math.min(score, 0.5);
116
+ suggestions.push("Fewer than 3 domains detected. Consider expanding capability coverage.");
117
+ }
118
+
119
+ return { name: "Capability Distribution", score, findings, suggestions };
120
+ }
121
+
122
+ function analyzeProtocolCompliance(genesDir: string, phenotypes: Map<string, Phenotype>): DimensionResult {
123
+ const findings: string[] = [];
124
+ const suggestions: string[] = [];
125
+ let totalScore = 0;
126
+
127
+ for (const [name, pheno] of phenotypes) {
128
+ let geneScore = 0;
129
+ const missing: string[] = [];
130
+
131
+ if (pheno.description) geneScore += 0.1;
132
+ else missing.push("description");
133
+
134
+ if (pheno.inputSchema?.properties && Object.keys(pheno.inputSchema.properties).length > 0) geneScore += 0.15;
135
+ else missing.push("inputSchema.properties");
136
+
137
+ if (pheno.outputSchema?.properties && Object.keys(pheno.outputSchema.properties).length > 0) geneScore += 0.15;
138
+ else missing.push("outputSchema.properties");
139
+
140
+ if (pheno.tags && pheno.tags.length > 0) geneScore += 0.1;
141
+ else missing.push("tags");
142
+
143
+ if (pheno.author) geneScore += 0.05;
144
+ else missing.push("author");
145
+
146
+ if (pheno.version) geneScore += 0.05;
147
+ else missing.push("version");
148
+
149
+ if (pheno.fidelity === "Native" && !hasImplementation(genesDir, name)) {
150
+ findings.push(`${name}: declares Native fidelity but has no index.ts/index.js`);
151
+ geneScore = Math.max(0, geneScore - 0.2);
152
+ }
153
+
154
+ if (missing.length > 0) {
155
+ findings.push(`${name}: missing ${missing.join(", ")}`);
156
+ }
157
+
158
+ totalScore += geneScore / 0.6;
159
+ }
160
+
161
+ const score = phenotypes.size > 0 ? Math.min(1, totalScore / phenotypes.size) : 0;
162
+
163
+ if (findings.length > 0) {
164
+ suggestions.push(`${findings.length} compliance issues found. Run with verbose=true for per-gene details.`);
165
+ }
166
+
167
+ return { name: "Protocol Compliance", score, findings, suggestions };
168
+ }
169
+
170
+ function analyzeSecurityVulnerabilities(genesDir: string, phenotypes: Map<string, Phenotype>): DimensionResult {
171
+ const findings: string[] = [];
172
+ const suggestions: string[] = [];
173
+
174
+ const dangerousPatterns: Array<{ pattern: RegExp; label: string }> = [
175
+ { pattern: /eval\s*\(/, label: "eval()" },
176
+ { pattern: /new\s+Function\s*\(/, label: "new Function()" },
177
+ { pattern: /child_process/, label: "child_process" },
178
+ { pattern: /sk-[a-zA-Z0-9]{20,}/, label: "hardcoded OpenAI key" },
179
+ { pattern: /ghp_[a-zA-Z0-9]{36}/, label: "hardcoded GitHub PAT" },
180
+ { pattern: /glpat-[a-zA-Z0-9]{20}/, label: "hardcoded GitLab PAT" },
181
+ { pattern: /AKIA[A-Z0-9]{16}/, label: "hardcoded AWS access key" },
182
+ ];
183
+
184
+ let vulnerableCount = 0;
185
+
186
+ for (const [name] of phenotypes) {
187
+ const source = readImplementation(genesDir, name);
188
+ if (!source) continue;
189
+
190
+ const geneIssues: string[] = [];
191
+ for (const { pattern, label } of dangerousPatterns) {
192
+ if (pattern.test(source)) {
193
+ geneIssues.push(label);
194
+ }
195
+ }
196
+
197
+ if (geneIssues.length > 0) {
198
+ vulnerableCount++;
199
+ findings.push(`${name}: detected ${geneIssues.join(", ")}`);
200
+ suggestions.push(`${name}: remove or sandbox dangerous patterns (${geneIssues.join(", ")})`);
201
+ }
202
+ }
203
+
204
+ const genesWithSource = [...phenotypes.keys()].filter(
205
+ (n) => readImplementation(genesDir, n) !== null
206
+ ).length;
207
+ const score = genesWithSource > 0 ? 1.0 - vulnerableCount / genesWithSource : 1.0;
208
+
209
+ return { name: "Security Vulnerabilities", score: Math.max(0, score), findings, suggestions };
210
+ }
211
+
212
+ function analyzeQualityScore(genesDir: string, phenotypes: Map<string, Phenotype>): DimensionResult {
213
+ const findings: string[] = [];
214
+ const suggestions: string[] = [];
215
+ let totalQuality = 0;
216
+
217
+ const genesParent = join(genesDir, "..");
218
+
219
+ for (const [name, pheno] of phenotypes) {
220
+ let q = 0;
221
+ const missing: string[] = [];
222
+
223
+ if (pheno.description) q += 0.1;
224
+ else missing.push("description");
225
+
226
+ if (pheno.tags && pheno.tags.length > 0) q += 0.1;
227
+ else missing.push("tags");
228
+
229
+ const hasInputProps = pheno.inputSchema?.properties && Object.keys(pheno.inputSchema.properties).length > 0;
230
+ const hasOutputProps = pheno.outputSchema?.properties && Object.keys(pheno.outputSchema.properties).length > 0;
231
+ if (hasInputProps && hasOutputProps) q += 0.3;
232
+ else missing.push("complete schema");
233
+
234
+ if (hasImplementation(genesDir, name)) q += 0.3;
235
+ else missing.push("express() implementation");
236
+
237
+ const testPath = join(genesParent, "tests", "unit", `${name}.test.ts`);
238
+ if (existsSync(testPath)) q += 0.2;
239
+ else missing.push("unit tests");
240
+
241
+ if (missing.length > 0) {
242
+ findings.push(`${name} (${q.toFixed(2)}): missing ${missing.join(", ")}`);
243
+ }
244
+
245
+ totalQuality += q;
246
+ }
247
+
248
+ const score = phenotypes.size > 0 ? totalQuality / phenotypes.size : 0;
249
+
250
+ if (score < 0.7) {
251
+ suggestions.push("Average quality below 0.7. Prioritize adding tests and completing schemas.");
252
+ }
253
+
254
+ return { name: "Quality Score", score: Math.min(1, score), findings, suggestions };
255
+ }
256
+
257
+ function analyzeEvolutionSuggestions(genesDir: string, phenotypes: Map<string, Phenotype>): DimensionResult {
258
+ const findings: string[] = [];
259
+ const suggestions: string[] = [];
260
+ let actionable = 0;
261
+ let total = 0;
262
+
263
+ for (const [name, pheno] of phenotypes) {
264
+ total++;
265
+ let needsWork = false;
266
+
267
+ if (pheno.fidelity === "Wrapped") {
268
+ suggestions.push(`${name}: Wrapped → consider upgrading to Hybrid or Native for better portability`);
269
+ needsWork = true;
270
+ }
271
+
272
+ const hasInputProps = pheno.inputSchema?.properties && Object.keys(pheno.inputSchema.properties).length > 0;
273
+ const hasOutputProps = pheno.outputSchema?.properties && Object.keys(pheno.outputSchema.properties).length > 0;
274
+ if (!hasInputProps || !hasOutputProps) {
275
+ suggestions.push(`${name}: incomplete schema — add inputSchema/outputSchema properties`);
276
+ needsWork = true;
277
+ }
278
+
279
+ if (!hasImplementation(genesDir, name)) {
280
+ suggestions.push(`${name}: no express() implementation — add index.ts`);
281
+ needsWork = true;
282
+ }
283
+
284
+ if (needsWork) actionable++;
285
+ }
286
+
287
+ findings.push(`${actionable}/${total} genes have evolution opportunities`);
288
+ const score = total > 0 ? 1.0 - actionable / total : 1.0;
289
+
290
+ return { name: "Evolution Suggestions", score: Math.max(0, score), findings, suggestions };
291
+ }
292
+
293
+ function analyzeRuntimeHealth(phenotypes: Map<string, Phenotype>): DimensionResult {
294
+ const findings: string[] = [];
295
+ const suggestions: string[] = [];
296
+ const logDir = join(homedir(), ".rotifer", "run-logs");
297
+ let totalScore = 0;
298
+ let genesWithLogs = 0;
299
+
300
+ for (const [name] of phenotypes) {
301
+ const logPath = join(logDir, `${name}.jsonl`);
302
+ if (!existsSync(logPath)) continue;
303
+
304
+ genesWithLogs++;
305
+ try {
306
+ const lines = readFileSync(logPath, "utf-8")
307
+ .split("\n")
308
+ .filter((l) => l.trim().length > 0);
309
+
310
+ let successes = 0;
311
+ let totalDuration = 0;
312
+ let parsed = 0;
313
+
314
+ for (const line of lines) {
315
+ try {
316
+ const entry = JSON.parse(line) as RunLogEntry;
317
+ parsed++;
318
+ if (entry.success) successes++;
319
+ totalDuration += entry.durationMs ?? 0;
320
+ } catch {
321
+ /* skip malformed lines */
322
+ }
323
+ }
324
+
325
+ if (parsed > 0) {
326
+ const successRate = successes / parsed;
327
+ const avgDuration = totalDuration / parsed;
328
+ findings.push(`${name}: ${parsed} runs, ${(successRate * 100).toFixed(1)}% success, avg ${avgDuration.toFixed(0)}ms`);
329
+ totalScore += successRate;
330
+ if (successRate < 0.8) {
331
+ suggestions.push(`${name}: success rate ${(successRate * 100).toFixed(1)}% — investigate failures`);
332
+ }
333
+ }
334
+ } catch {
335
+ findings.push(`${name}: log file unreadable`);
336
+ }
337
+ }
338
+
339
+ const score = genesWithLogs > 0 ? totalScore / genesWithLogs : 1.0;
340
+
341
+ if (genesWithLogs === 0) {
342
+ findings.push("No runtime logs found — genes have not been executed yet");
343
+ }
344
+
345
+ return { name: "Runtime Health", score: Math.max(0, Math.min(1, score)), findings, suggestions };
346
+ }
347
+
348
+ function analyzeDependencyChains(genesDir: string, phenotypes: Map<string, Phenotype>): DimensionResult {
349
+ const findings: string[] = [];
350
+ const suggestions: string[] = [];
351
+ let brokenDeps = 0;
352
+ let totalDeps = 0;
353
+
354
+ for (const [name, pheno] of phenotypes) {
355
+ const deps = pheno.dependencies ?? [];
356
+ for (const dep of deps) {
357
+ totalDeps++;
358
+ if (!phenotypes.has(dep) && !existsSync(join(genesDir, dep))) {
359
+ brokenDeps++;
360
+ findings.push(`${name} → ${dep}: dependency not found`);
361
+ suggestions.push(`${name}: install or create missing dependency "${dep}"`);
362
+ }
363
+ }
364
+ }
365
+
366
+ if (totalDeps === 0) {
367
+ findings.push("No inter-gene dependencies declared");
368
+ } else {
369
+ findings.push(`${totalDeps} dependencies checked, ${brokenDeps} broken`);
370
+ }
371
+
372
+ const score = totalDeps > 0 ? 1.0 - brokenDeps / totalDeps : 1.0;
373
+
374
+ return { name: "Dependency Chain Health", score: Math.max(0, score), findings, suggestions };
375
+ }
376
+
377
+ export function express(input: HealthInput): HealthOutput {
378
+ const genesDir = input.genesDir ?? "./genes";
379
+ const phenotypes = loadPhenotypes(genesDir);
380
+ const geneCount = phenotypes.size;
381
+
382
+ const dimensions: DimensionResult[] = [
383
+ analyzeCapabilityDistribution(phenotypes),
384
+ analyzeProtocolCompliance(genesDir, phenotypes),
385
+ analyzeSecurityVulnerabilities(genesDir, phenotypes),
386
+ analyzeQualityScore(genesDir, phenotypes),
387
+ analyzeEvolutionSuggestions(genesDir, phenotypes),
388
+ analyzeRuntimeHealth(phenotypes),
389
+ analyzeDependencyChains(genesDir, phenotypes),
390
+ ];
391
+
392
+ const overallScore = dimensions.reduce((sum, d) => sum + d.score, 0) / dimensions.length;
393
+
394
+ const ranked = [...dimensions].sort((a, b) => a.score - b.score);
395
+ const recommendations: string[] = [];
396
+ for (const dim of ranked.slice(0, 3)) {
397
+ if (dim.score < 0.9 && dim.suggestions.length > 0) {
398
+ recommendations.push(`[${dim.name}] ${dim.suggestions[0]}`);
399
+ }
400
+ }
401
+
402
+ if (!input.verbose) {
403
+ for (const dim of dimensions) {
404
+ if (dim.findings.length > 5) dim.findings = dim.findings.slice(0, 5);
405
+ if (dim.suggestions.length > 3) dim.suggestions = dim.suggestions.slice(0, 3);
406
+ }
407
+ }
408
+
409
+ const grade =
410
+ overallScore >= 0.9 ? "Excellent" :
411
+ overallScore >= 0.7 ? "Good" :
412
+ overallScore >= 0.5 ? "Fair" :
413
+ "Needs Attention";
414
+
415
+ const summary = `Gene Library Health: ${grade} (${(overallScore * 100).toFixed(1)}%) — ${geneCount} genes scanned across 7 dimensions`;
416
+
417
+ return { summary, dimensions, overallScore, geneCount, recommendations };
418
+ }
419
+
420
+ export function display(output: HealthOutput, options?: { verbose?: boolean }): void {
421
+ const RESET = "\x1b[0m";
422
+ const BOLD = "\x1b[1m";
423
+ const DIM = "\x1b[2m";
424
+ const RED = "\x1b[31m";
425
+ const GREEN = "\x1b[32m";
426
+ const YELLOW = "\x1b[33m";
427
+ const BLUE = "\x1b[34m";
428
+ const CYAN = "\x1b[36m";
429
+
430
+ const bar = (score: number, width = 20): string => {
431
+ const clamped = Math.max(0, Math.min(1, score));
432
+ const filled = Math.round(clamped * width);
433
+ return `${GREEN}${"█".repeat(filled)}${DIM}${"░".repeat(Math.max(0, width - filled))}${RESET}`;
434
+ };
435
+
436
+ const dimIcon = (score: number): string => {
437
+ if (score > 0.7) return `${GREEN}✓${RESET}`;
438
+ if (score > 0.4) return `${YELLOW}!${RESET}`;
439
+ return `${RED}✗${RESET}`;
440
+ };
441
+
442
+ console.log(`${BOLD}${CYAN}Gene Health Report${RESET}`);
443
+ console.log(`${DIM}${output.summary}${RESET}`);
444
+ console.log();
445
+ console.log(
446
+ `${BOLD}Overall${RESET} ${bar(output.overallScore)} ${BLUE}${(output.overallScore * 100).toFixed(1)}%${RESET} ${DIM}(${output.geneCount} genes)${RESET}`
447
+ );
448
+ console.log();
449
+
450
+ const maxNameLen = Math.max(...output.dimensions.map((d) => d.name.length));
451
+
452
+ for (const d of output.dimensions) {
453
+ const icon = dimIcon(d.score);
454
+ const padded = d.name.padEnd(maxNameLen);
455
+ const pct = `${(d.score * 100).toFixed(0)}%`.padStart(4);
456
+ console.log(
457
+ `${icon} ${BOLD}${padded}${RESET} ${bar(d.score)} ${CYAN}${pct}${RESET}`
458
+ );
459
+ if (options?.verbose && d.findings.length > 0) {
460
+ console.log(` ${DIM}Findings:${RESET}`);
461
+ for (const f of d.findings) {
462
+ console.log(` ${DIM}•${RESET} ${f}`);
463
+ }
464
+ }
465
+ }
466
+
467
+ console.log();
468
+ console.log(`${BOLD}Recommendations${RESET}`);
469
+ if (output.recommendations.length === 0) {
470
+ console.log(` ${DIM}(none)${RESET}`);
471
+ } else {
472
+ for (const r of output.recommendations) {
473
+ console.log(` ${YELLOW}→${RESET} ${r}`);
474
+ }
475
+ }
476
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "gene-health-scanner",
3
+ "version": "0.2.0",
4
+ "domain": "meta.diagnostics",
5
+ "fidelity": "Native",
6
+ "description": "Seven-dimensional gene library health scanner — diagnoses capability distribution, protocol compliance, security, quality, evolution paths, runtime health, and dependency chains",
7
+ "author": "Rotifer Protocol Contributors",
8
+ "license": "Apache-2.0",
9
+ "inputSchema": {
10
+ "type": "object",
11
+ "properties": {
12
+ "genesDir": { "type": "string", "description": "Path to genes directory (defaults to ./genes)" },
13
+ "verbose": { "type": "boolean", "default": false, "description": "Output detailed Level 3 report" }
14
+ }
15
+ },
16
+ "outputSchema": {
17
+ "type": "object",
18
+ "properties": {
19
+ "summary": { "type": "string" },
20
+ "dimensions": {
21
+ "type": "array",
22
+ "items": {
23
+ "type": "object",
24
+ "properties": {
25
+ "name": { "type": "string" },
26
+ "score": { "type": "number" },
27
+ "findings": { "type": "array", "items": { "type": "string" } },
28
+ "suggestions": { "type": "array", "items": { "type": "string" } }
29
+ }
30
+ }
31
+ },
32
+ "overallScore": { "type": "number" },
33
+ "geneCount": { "type": "integer" },
34
+ "recommendations": { "type": "array", "items": { "type": "string" } }
35
+ },
36
+ "required": ["summary", "dimensions", "overallScore"]
37
+ },
38
+ "displayField": "summary",
39
+ "tags": ["diagnostics", "quality", "meta", "gene-library", "health-check"],
40
+ "dependencies": [],
41
+ "transparency": "Open",
42
+ "semanticRequirements": {
43
+ "timeModel": "Sync",
44
+ "concurrencyModel": "Stateless",
45
+ "failureSemantics": "Retry"
46
+ }
47
+ }
@@ -18,8 +18,10 @@ interface SearchOutput {
18
18
  /**
19
19
  * Genesis Gene: Web Search
20
20
  *
21
- * Full-featured web search gene. In MVP, returns simulated results.
22
- * Production binding would connect to a real search API.
21
+ * Demo of a search gene's shape: it returns simulated example.com results and
22
+ * searches nothing. The phenotype description, the README and every result
23
+ * title say so (#452), so neither a person nor an agent reading the output can
24
+ * mistake it for a live search.
23
25
  */
24
26
  export function express(input: SearchInput): SearchOutput {
25
27
  const start = Date.now();
@@ -45,7 +47,7 @@ export function express(input: SearchInput): SearchOutput {
45
47
  : 5;
46
48
 
47
49
  const results: SearchResult[] = Array.from({ length: max }, (_, i) => ({
48
- title: `Result ${i + 1} for "${query}"`,
50
+ title: `[Simulated] Result ${i + 1} for "${query}"`,
49
51
  url: `https://example.com/search?q=${encodeURIComponent(query)}&p=${i + 1}`,
50
52
  snippet: `This is a simulated search result #${i + 1} for the query "${query}". In production, this gene binds to a real search provider.`,
51
53
  }));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "domain": "search",
3
- "description": "Search the web and return structured results with titles, URLs, and snippets. Supports configurable result count.",
3
+ "description": "Demo gene: returns simulated search results (example.com URLs), not a live web search. Shows the shape of a search gene: structured results with titles, URLs and snippets, and a configurable result count.",
4
4
  "inputSchema": {
5
5
  "type": "object",
6
6
  "properties": {
@@ -48,7 +48,7 @@
48
48
  }
49
49
  },
50
50
  "dependencies": [],
51
- "version": "0.2.0",
51
+ "version": "0.2.1",
52
52
  "author": "rotifer-genesis",
53
53
  "fidelity": "Native",
54
54
  "transparency": "Open",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "domain": "search",
3
- "description": "Lightweight web search returning a single best-match answer with source attribution.",
3
+ "description": "Demo gene: returns one simulated answer (example.com source), not a live web search. Shows the shape of a lightweight search gene with source attribution.",
4
4
  "inputSchema": {
5
5
  "type": "object",
6
6
  "properties": {
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "dependencies": [],
28
- "version": "0.2.0",
28
+ "version": "0.2.1",
29
29
  "author": "rotifer-genesis",
30
30
  "fidelity": "Native",
31
31
  "transparency": "Open",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rotifer/playground",
3
- "version": "0.30.2",
3
+ "version": "0.31.0",
4
4
  "description": "Rotifer Protocol Playground — local development environment for gene development, Arena competition, and protocol simulation",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Rotifer Protocol Contributors",
@@ -92,10 +92,10 @@
92
92
  },
93
93
  "optionalDependencies": {
94
94
  "@rolldown/binding-linux-x64-gnu": "1.1.1",
95
- "@rotifer/playground-darwin-arm64": "0.30.2",
96
- "@rotifer/playground-darwin-x64": "0.30.2",
97
- "@rotifer/playground-linux-x64-gnu": "0.30.2",
98
- "@rotifer/playground-win32-x64-msvc": "0.30.2"
95
+ "@rotifer/playground-darwin-arm64": "0.31.0",
96
+ "@rotifer/playground-darwin-x64": "0.31.0",
97
+ "@rotifer/playground-linux-x64-gnu": "0.31.0",
98
+ "@rotifer/playground-win32-x64-msvc": "0.31.0"
99
99
  },
100
100
  "engines": {
101
101
  "node": ">=22.18.0"