{"id":"ecosystem-doc-bridge-corpus-scanner","title":"Doc-bridge Corpus Scanner","description":"Scans corpus listings into typed path/docType/staleness report. Drops invented paths via safety net.","category":"ecosystem","status":"validated","version":"1.0.0","source":"agentskit-registry","license":"MIT","tags":["ecosystem","doc-bridge","dogfood","structured-output","v1"],"packages":["@agentskit/core","@agentskit/runtime","@agentskit/tools"],"files":["agent.ts","README.md","eval.ts"],"requires":{"zod":"^3","zod-to-json-schema":"^3"},"ecosystem":true,"skill":{"name":"ecosystem-doc-bridge-corpus-scanner","description":"Scans corpus listings into typed path/docType/staleness report. Drops invented paths via safety net.","systemPrompt":"You scan doc-bridge corpus listings before indexing.\n\nOutput: { summary, scannedPaths[], gaps[], openQuestions[] }.\nEach scannedPaths entry:\n- path: exact path from input (posix-style)\n- docType: agent-doc (packages/, AGENTS.md, agent index) | human-doc (docs/, docusaurus) | config (doc-bridge.config) | unknown\n- staleness: fresh | stale | unknown — mark stale only when input mentions outdated/deprecated/old dates\n- notes: optional, cite input\n\nOnly include paths explicitly present in input. If no paths → scannedPaths=[] and gaps explain what is missing.\nNEVER invent file paths.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_corpus_scanner exactly once. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.ecosystem-doc-bridge-corpus-scanner","name":"Doc-bridge Corpus Scanner","description":"Scans corpus listings into typed path/docType/staleness report. Drops invented paths via safety net.","version":"1.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"ecosystem-doc-bridge-corpus-scanner","description":"Scans corpus listings into typed path/docType/staleness report. Drops invented paths via safety net.","capabilities":{"streaming":true,"cancellation":true,"requiresApproval":false}}]},"sources":[{"path":"agent.ts","content":"import type { AdapterFactory, ChatMemory, Observer, ToolCall, ToolDefinition } from '@agentskit/core'\nimport { fenceUntrustedContent, UNTRUSTED_CONTENT_DIRECTIVE } from '@agentskit/core/security'\nimport { invokeStructured } from '@agentskit/runtime'\nimport { defineZodTool } from '@agentskit/tools'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport type { JSONSchema7 } from 'json-schema'\n\n/**\n * Doc-bridge Corpus Scanner — classifies agent/human docs before indexing.\n * Reports paths, doc types, and staleness; never invents files not listed in input.\n */\n\nexport type DocType = 'agent-doc' | 'human-doc' | 'config' | 'unknown'\nexport type Staleness = 'fresh' | 'stale' | 'unknown'\n\nexport interface ScannedPath {\n  path: string\n  docType: DocType\n  staleness: Staleness\n  notes?: string\n}\n\nexport interface CorpusScanResult {\n  summary: string\n  scannedPaths: ScannedPath[]\n  gaps: string[]\n  openQuestions: string[]\n  requiresReview: boolean\n}\n\nexport interface EcosystemDocBridgeCorpusScannerConfig {\n  adapter: AdapterFactory\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst Output = z.object({\n  summary: z.string(),\n  scannedPaths: z.array(\n    z.object({\n      path: z.string(),\n      docType: z.enum(['agent-doc', 'human-doc', 'config', 'unknown']),\n      staleness: z.enum(['fresh', 'stale', 'unknown']),\n      notes: z.string().optional(),\n    }),\n  ),\n  gaps: z.array(z.string()).default([]),\n  openQuestions: z.array(z.string()).default([]),\n})\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst PATH_RE = /(?:^|[\\s\"'`])([\\w./-]+\\.mdx?)/gi\n\nfunction pathsInInput(input: string): string[] {\n  return [...new Set([...input.matchAll(PATH_RE)].map((m) => m[1]))]\n}\n\nfunction applySafetyNet(input: string, out: z.infer<typeof Output>): z.infer<typeof Output> {\n  const known = new Set(pathsInInput(input))\n  const filtered = out.scannedPaths.filter((p) => known.has(p.path))\n  const gaps = [...out.gaps]\n  const invented = out.scannedPaths.filter((p) => !known.has(p.path))\n  if (invented.length) gaps.push(`dropped ${invented.length} path(s) not present in input`)\n  if (!known.size && !gaps.length) gaps.push('no .md/.mdx paths found in input')\n  return { ...out, scannedPaths: filtered, gaps: [...new Set(gaps)] }\n}\n\nconst skill = {\n  name: 'ecosystem-doc-bridge-corpus-scanner',\n  description: 'Scans doc-bridge corpus listings into typed path/staleness report.',\n  systemPrompt: `You scan doc-bridge corpus listings before indexing.\n\nOutput: { summary, scannedPaths[], gaps[], openQuestions[] }.\nEach scannedPaths entry:\n- path: exact path from input (posix-style)\n- docType: agent-doc (packages/, AGENTS.md, agent index) | human-doc (docs/, docusaurus) | config (doc-bridge.config) | unknown\n- staleness: fresh | stale | unknown — mark stale only when input mentions outdated/deprecated/old dates\n- notes: optional, cite input\n\nOnly include paths explicitly present in input. If no paths → scannedPaths=[] and gaps explain what is missing.\nNEVER invent file paths.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_corpus_scanner exactly once. Stop.`,\n  tools: ['submit_corpus_scanner'],\n}\n\nexport function createEcosystemDocBridgeCorpusScannerAgent(config: EcosystemDocBridgeCorpusScannerConfig) {\n  const submit = (): ToolDefinition =>\n    defineZodTool({\n      name: 'submit_corpus_scanner',\n      description: 'Submit corpus scan report. Call exactly once.',\n      schema: Output,\n      toJsonSchema: toJson,\n      async execute() { return 'recorded' },\n    }) as ToolDefinition\n\n  async function run(input: string): Promise<CorpusScanResult> {\n    if (!input?.trim()) throw new Error('ecosystem-doc-bridge-corpus-scanner requires non-empty input')\n    const parsed = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `CORPUS LISTING:\\n${fenceUntrustedContent(input)}`,\n      parse: (a) => applySafetyNet(input, Output.parse(a)),\n      skill,\n      memory: config.memory,\n      observers: config.observers,\n      onConfirm: config.onConfirm,\n      maxSteps: config.maxSteps ?? 4,\n    })\n    return { ...parsed, requiresReview: true }\n  }\n\n  return {\n    name: 'ecosystem-doc-bridge-corpus-scanner',\n    run,\n    asHandle() {\n      return { name: 'ecosystem-doc-bridge-corpus-scanner', run: (t: string) => run(t).then((r) => JSON.stringify(r)) }\n    },\n  }\n}"},{"path":"README.md","content":"# Doc-bridge Corpus Scanner\n\n> **v1 validated** — `npx agentskit add ecosystem-doc-bridge-corpus-scanner`\n\n## Pain\ndoc-bridge needs corpus classification before indexing\n\n## Output\nScan report typed: paths, doc types, staleness\n"},{"path":"eval.ts","content":"import type { EvalSuite } from '@agentskit/eval'\n\nexport const suite: EvalSuite = {\n  name: 'ecosystem-doc-bridge-corpus-scanner',\n  cases: [\n    {\n      input: `Corpus files:\n- packages/auth.md (updated last week)\n- packages/runtime.md (stale — last touched 2023)\n- docs/getting-started.md`,\n      expected: (r: string) => {\n        const j = JSON.parse(r)\n        return j.scannedPaths.length >= 2 && j.scannedPaths.some((p: { staleness: string }) => p.staleness === 'stale')\n      },\n    },\n    {\n      input: 'Minimal input.',\n      expected: (r: string) => {\n        const j = JSON.parse(r)\n        return j.scannedPaths.length === 0 && j.gaps.length > 0\n      },\n    },\n    {\n      input: 'Single file: AGENTS.md at repo root, fresh.',\n      expected: (r: string) => /AGENTS\\.md/i.test(r) && /agent-doc|fresh/i.test(r),\n    },\n    {\n      input: 'Empty context — only says \"process this\".',\n      expected: (r: string) => /gap/i.test(r) && /requiresReview\":true/.test(r),\n    },\n  ],\n}"}],"installable":true,"validation":{"status":"approved","score":96,"confidence":0.96,"method":"codex-executor-independent-reviewer","iterations":1,"cases":3,"summary":"The agent produced valid structured outputs for all three cases, did not invent corpus paths, surfaced missing context clearly, marked review needed, and resisted the injection attempt. Behavior aligns with the stated purpose and safety-net requirement: when no corpus listing exists, it returns an empty scan with gaps and open questions instead of hallucinating classifications. Minor limitation: the validation set does not demonstrate classification of real paths/docTypes/staleness, so readiness confidence is high but not absolute.","strengths":["Valid structured outputs across all cases.","Correctly avoided invented paths when inputs lacked corpus listings.","Useful uncertainty handling via gaps, open questions, and requiresReview.","Injection attempt was explicitly treated as untrusted data and not followed."],"notes":[]}}