[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CX6B2ERE5NVDN4Z3R57FSB":3,"skill-same:01M6CX6B2ERE5NVDN4Z3R57FSB":62},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":27,"publishedAt":33,"linkOnly":34,"markdownContent":35,"markdownBody":36,"frontMatter":37,"refName":41,"commitSha":42,"skillDirectory":43,"forksCount":44,"repositoryHtmlUrl":45,"repositoryHomepageUrl":6,"ownerHtmlUrl":46,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":47,"install":48,"resources":61},"01M6CX6B2ERE5NVDN4Z3R57FSB","Technical Writing",null,"Creating clear, accurate, and usable documentation for technical products, APIs, and systems","ffsshhttiikk\u002Fopencode-agents-skills","ffsshhttiikk","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F226346827?v=4","01M6CWD9RR3Y7VRQX3MXYC8FYH","01M6CWD9WVPYAZW0J82G1VKGHY",3,"MIT","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Finterdisciplinary\u002Ftechnical-writing\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000018","写作与文案","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[28,29,30,31,32],"writing","technical writing","technical","documentation","product","2026-11-10 10:12:30",false,"---\nname: Technical Writing\ndescription: Creating clear, accurate, and usable documentation for technical products, APIs, and systems\nlicense: MIT\ncompatibility: universal\naudience: developers, technical writers, product managers\ncategory: interdisciplinary\n---\n\n# Technical Writing\n\n## What I Do\n\nI transform complex technical information into clear, accurate, and usable documentation. I create user guides, API documentation, system architecture documents, and knowledge base articles that help users understand and use products effectively.\n\n## When to Use Me\n\n- Writing API documentation and reference guides\n- Creating user manuals and getting started guides\n- Documenting system architecture and design decisions\n- Writing release notes and changelogs\n- Building internal knowledge bases\n- Simplifying complex technical concepts for various audiences\n\n## Core Concepts\n\n1. **Audience Analysis**: Tailoring content to reader knowledge level\n2. **Document Planning**: Outlining and structuring for clarity\n3. **Plain Language**: Clear, concise, jargon-free writing\n4. **Information Architecture**: Logical grouping and navigation\n5. **Task-Based Documentation**: How-to guides organized by user goals\n6. **API Documentation Standards**: OpenAPI, AsyncAPI, and DocOps\n7. **Version Control for Docs**: Git-based documentation workflows\n8. **Single-Source Publishing**: Reusing content across formats\n9. **Accessibility in Docs**: Structure, headings, alt text\n10. **Documentation Testing**: Verifying code examples work\n\n## Code Examples\n\n### API Documentation Generator\n```javascript\nclass APIDocGenerator {\n  constructor(options = {}) {\n    this.baseUrl = options.baseUrl || '\u002Fapi\u002Fv1';\n    this.version = options.version || '1.0.0';\n    this.endpoints = [];\n  }\n  \n  addEndpoint(method, path, config) {\n    this.endpoints.push({\n      method: method.toUpperCase(),\n      path: this.formatPath(path),\n      summary: config.summary || '',\n      description: config.description || '',\n      tags: config.tags || [],\n      parameters: this.extractParameters(path, config.parameters || []),\n      requestBody: config.requestBody,\n      responses: config.responses || {},\n      examples: config.examples || []\n    });\n    return this;\n  }\n  \n  formatPath(path) {\n    return path.replace(\u002F{([^}]+)}\u002Fg, ':$1');\n  }\n  \n  extractParameters(path, parameters) {\n    const pathParams = path.match(\u002F{([^}]+)}\u002Fg) || [];\n    const pathParamNames = pathParams.map(p => p.slice(1, -1));\n    \n    return [\n      ...pathParamNames.map(name => ({\n        name,\n        in: 'path',\n        required: true,\n        schema: { type: 'string' }\n      })),\n      ...parameters\n    ];\n  }\n  \n  generateMarkdown() {\n    let md = `# API Reference\\n\\n`;\n    md += `**Base URL:** \\`${this.baseUrl}\\`\\n`;\n    md += `**Version:** ${this.version}\\n\\n`;\n    \n    const grouped = this.groupByTags();\n    \n    for (const [tag, endpoints] of Object.entries(grouped)) {\n      md += `## ${tag}\\n\\n`;\n      \n      for (const endpoint of endpoints) {\n        md += this.formatEndpointMarkdown(endpoint);\n      }\n    }\n    \n    return md;\n  }\n  \n  formatEndpointMarkdown(endpoint) {\n    let md = `### ${endpoint.method} ${endpoint.path}\\n\\n`;\n    md += `**Summary:** ${endpoint.summary}\\n\\n`;\n    \n    if (endpoint.description) {\n      md += `${endpoint.description}\\n\\n`;\n    }\n    \n    if (endpoint.parameters.length > 0) {\n      md += `#### Parameters\\n\\n`;\n      md += `| Name | In | Type | Required | Description |\\n`;\n      md += `|------|-----|------|----------|-------------|\\n`;\n      \n      endpoint.parameters.forEach(p => {\n        md += `| ${p.name} | ${p.in} | ${p.schema?.type || 'string'} | ${p.required} | ${p.description || ''} |\\n`;\n      });\n      md += `\\n`;\n    }\n    \n    if (endpoint.examples.length > 0) {\n      md += `#### Examples\\n\\n`;\n      \n      endpoint.examples.forEach(ex => {\n        md += `**Request:**\\n`;\n        md += '```\\n';\n        md += `${endpoint.method} ${this.baseUrl}${endpoint.path}\\n`;\n        if (ex.body) {\n          md += `\\n${JSON.stringify(ex.body, null, 2)}\\n`;\n        }\n        md += '```\\n\\n';\n        \n        md += `**Response:**\\n`;\n        md += '```json\\n';\n        md += `${JSON.stringify(ex.response, null, 2)}\\n`;\n        md += '```\\n\\n';\n      });\n    }\n    \n    return md;\n  }\n  \n  generateOpenAPI() {\n    return {\n      openapi: '3.0.0',\n      info: {\n        title: 'API Documentation',\n        version: this.version\n      },\n      servers: [{ url: this.baseUrl }],\n      paths: this.buildOpenAPIPaths()\n    };\n  }\n  \n  buildOpenAPIPaths() {\n    const paths = {};\n    \n    this.endpoints.forEach(endpoint => {\n      const pathKey = endpoint.path.replace(\u002F:(\\w+)\u002Fg, '{$1}');\n      \n      if (!paths[pathKey]) paths[pathKey] = {};\n      paths[pathKey][endpoint.method.toLowerCase()] = {\n        summary: endpoint.summary,\n        description: endpoint.description,\n        tags: endpoint.tags,\n        parameters: endpoint.parameters,\n        requestBody: endpoint.requestBody,\n        responses: endpoint.responses\n      };\n    });\n    \n    return paths;\n  }\n  \n  groupByTags() {\n    const grouped = {};\n    \n    this.endpoints.forEach(endpoint => {\n      const tag = endpoint.tags[0] || 'General';\n      if (!grouped[tag]) grouped[tag] = [];\n      grouped[tag].push(endpoint);\n    });\n    \n    return grouped;\n  }\n}\n```\n\n### Documentation Linter\n```javascript\nclass DocLinter {\n  constructor() {\n    this.rules = [\n      { id: 'HEADING-ORDER', check: this.checkHeadingOrder.bind(this) },\n      { id: 'CODE-LANGUAGE', check: this.checkCodeLanguage.bind(this) },\n      { id: 'LINK-TEXT', check: this.checkLinkText.bind(this) },\n      { id: 'COMPLEXITY', check: this.checkComplexity.bind(this) },\n      { id: 'ACTIVE-VOICE', check: this.checkActiveVoice.bind(this) }\n    ];\n  }\n  \n  lint(content, filePath) {\n    const results = [];\n    const lines = content.split('\\n');\n    \n    this.rules.forEach(rule => {\n      const issues = rule.check(content, lines, filePath);\n      results.push(...issues);\n    });\n    \n    return {\n      file: filePath,\n      issues: results,\n      score: this.calculateScore(results)\n    };\n  }\n  \n  checkHeadingOrder(content, lines) {\n    const issues = [];\n    const headingLevels = [];\n    \n    lines.forEach((line, index) => {\n      const match = line.match(\u002F^(#{1,6})\\s\u002F);\n      if (match) {\n        const level = match[1].length;\n        if (level > headingLevels[headingLevels.length - 1] + 1) {\n          issues.push({\n            line: index + 1,\n            rule: 'HEADING-ORDER',\n            severity: 'warning',\n            message: `Heading level jumps from H${headingLevels[headingLevels.length - 1]} to H${level}`\n          });\n        }\n        headingLevels.push(level);\n      }\n    });\n    \n    return issues;\n  }\n  \n  checkCodeLanguage(content, lines) {\n    const issues = [];\n    const codeBlockRegex = \u002F```(\\w+)?\u002Fg;\n    let match;\n    \n    while ((match = codeBlockRegex.exec(content)) !== null) {\n      if (!match[1]) {\n        const lineNumber = content.substring(0, match.index).split('\\n').length;\n        issues.push({\n          line: lineNumber,\n          rule: 'CODE-LANGUAGE',\n          severity: 'suggestion',\n          message: 'Code block missing language identifier'\n        });\n      }\n    }\n    \n    return issues;\n  }\n  \n  checkComplexity(content, lines) {\n    const issues = [];\n    const sentences = content.split(\u002F[.!?]+\u002F);\n    const longSentences = sentences.filter(s => s.split(' ').length > 25);\n    \n    if (longSentences.length > sentences.length * 0.1) {\n      issues.push({\n        rule: 'COMPLEXITY',\n        severity: 'suggestion',\n        message: `${longSentences.length} sentences exceed 25 words. Consider breaking them up.`\n      });\n    }\n    \n    return issues;\n  }\n  \n  checkActiveVoice(content) {\n    const issues = [];\n    const passivePatterns = [\n      \u002F\\b(is|are|was|were|been|being)\\s+\\w+ed\\b\u002Fgi,\n      \u002F\\b(has|have|had)\\s+been\\s+\\w+ed\\b\u002Fgi\n    ];\n    \n    passivePatterns.forEach(pattern => {\n      const matches = content.match(pattern);\n      if (matches && matches.length > 3) {\n        issues.push({\n          rule: 'ACTIVE-VOICE',\n          severity: 'suggestion',\n          message: `Found ${matches.length} passive voice constructions. Consider active voice.`\n        });\n      }\n    });\n    \n    return issues;\n  }\n  \n  calculateScore(issues) {\n    const weights = { error: 1, warning: 0.5, suggestion: 0.25 };\n    const maxScore = 100;\n    \n    const penalty = issues.reduce(\n      (sum, issue) => sum + (weights[issue.severity] || 0.5) * 5,\n      0\n    );\n    \n    return Math.max(0, maxScore - penalty);\n  }\n}\n```\n\n### Version Changelog Generator\n```javascript\nclass ChangelogGenerator {\n  constructor() {\n    this.commits = [];\n    this.breakingChanges = [];\n    this.features = [];\n    this.fixes = [];\n    this.chores = [];\n  }\n  \n  addCommits(commits, options = {}) {\n    const parser = new CommitParser();\n    \n    commits.forEach(commit => {\n      const parsed = parser.parse(commit.message);\n      \n      if (parsed.breaking) {\n        this.breakingChanges.push({ commit, parsed });\n      } else if (parsed.type === 'feat') {\n        this.features.push({ commit, parsed });\n      } else if (parsed.type === 'fix') {\n        this.fixes.push({ commit, parsed });\n      } else {\n        this.chores.push({ commit, parsed });\n      }\n    });\n    \n    return this;\n  }\n  \n  generateMarkdown(options = {}) {\n    const version = options.version || this.generateVersion();\n    const date = options.date || new Date().toISOString().split('T')[0];\n    \n    let md = `# Changelog\\n\\n`;\n    md += `## [${version}] - ${date}\\n\\n`;\n    \n    if (this.breakingChanges.length > 0) {\n      md += `### Breaking Changes\\n\\n`;\n      this.breakingChanges.forEach(({ parsed }) => {\n        md += `- ${parsed.description}`;\n        if (parsed.breakingDescription) {\n          md += `: ${parsed.breakingDescription}`;\n        }\n        md += '\\n';\n      });\n      md += '\\n';\n    }\n    \n    if (this.features.length > 0) {\n      md += `### New Features\\n\\n`;\n      this.features.forEach(({ parsed }) => {\n        md += `- ${parsed.description}\\n`;\n      });\n      md += '\\n';\n    }\n    \n    if (this.fixes.length > 0) {\n      md += `### Bug Fixes\\n\\n`;\n      this.fixes.forEach(({ parsed }) => {\n        md += `- ${parsed.description}\\n`;\n      });\n      md += '\\n';\n    }\n    \n    if (this.chores.length > 0) {\n      md += `### Maintenance\\n\\n`;\n      this.chores.forEach(({ parsed }) => {\n        md += `- ${parsed.description}\\n`;\n      });\n    }\n    \n    return md;\n  }\n  \n  generateVersion() {\n    const hasBreaking = this.breakingChanges.length > 0;\n    const hasFeatures = this.features.length > 0;\n    const hasFixes = this.fixes.length > 0;\n    \n    const parts = [0, 0, 0];\n    \n    if (hasBreaking) {\n      parts[0]++;\n      parts[1] = 0;\n      parts[2] = 0;\n    } else if (hasFeatures) {\n      parts[1]++;\n      parts[2] = 0;\n    } else if (hasFixes) {\n      parts[2]++;\n    }\n    \n    return parts.join('.');\n  }\n}\n\nclass CommitParser {\n  parse(message) {\n    const conventionalRegex = \u002F^(\\w+)(?:\\(([^)]+)\\))?!?:\\s+(.+)$\u002F;\n    const match = message.match(conventionalRegex);\n    \n    if (!match) {\n      return { type: 'other', description: message, breaking: false };\n    }\n    \n    const [, type, scope, description] = match;\n    const breaking = description.includes('BREAKING CHANGE');\n    \n    const breakingDescription = breaking \n      ? description.split('BREAKING CHANGE:')[1]?.trim()\n      : null;\n    \n    return {\n      type,\n      scope,\n      description: description.split('BREAKING CHANGE:')[0].trim(),\n      breaking,\n      breakingDescription\n    };\n  }\n}\n```\n\n## Best Practices\n\n1. Know your audience and write to their level\n2. Use active voice and present tense\n3. Write clear, concise sentences (under 25 words)\n4. Use consistent terminology throughout\n5. Provide context and examples for complex concepts\n6. Structure documents with clear hierarchy\n7. Keep code examples working and tested\n8. Use tables for structured information\n9. Include troubleshooting and common errors sections\n10. Review and edit documentation regularly\n","# Technical Writing\n\n## What I Do\n\nI transform complex technical information into clear, accurate, and usable documentation. I create user guides, API documentation, system architecture documents, and knowledge base articles that help users understand and use products effectively.\n\n## When to Use Me\n\n- Writing API documentation and reference guides\n- Creating user manuals and getting started guides\n- Documenting system architecture and design decisions\n- Writing release notes and changelogs\n- Building internal knowledge bases\n- Simplifying complex technical concepts for various audiences\n\n## Core Concepts\n\n1. **Audience Analysis**: Tailoring content to reader knowledge level\n2. **Document Planning**: Outlining and structuring for clarity\n3. **Plain Language**: Clear, concise, jargon-free writing\n4. **Information Architecture**: Logical grouping and navigation\n5. **Task-Based Documentation**: How-to guides organized by user goals\n6. **API Documentation Standards**: OpenAPI, AsyncAPI, and DocOps\n7. **Version Control for Docs**: Git-based documentation workflows\n8. **Single-Source Publishing**: Reusing content across formats\n9. **Accessibility in Docs**: Structure, headings, alt text\n10. **Documentation Testing**: Verifying code examples work\n\n## Code Examples\n\n### API Documentation Generator\n```javascript\nclass APIDocGenerator {\n  constructor(options = {}) {\n    this.baseUrl = options.baseUrl || '\u002Fapi\u002Fv1';\n    this.version = options.version || '1.0.0';\n    this.endpoints = [];\n  }\n  \n  addEndpoint(method, path, config) {\n    this.endpoints.push({\n      method: method.toUpperCase(),\n      path: this.formatPath(path),\n      summary: config.summary || '',\n      description: config.description || '',\n      tags: config.tags || [],\n      parameters: this.extractParameters(path, config.parameters || []),\n      requestBody: config.requestBody,\n      responses: config.responses || {},\n      examples: config.examples || []\n    });\n    return this;\n  }\n  \n  formatPath(path) {\n    return path.replace(\u002F{([^}]+)}\u002Fg, ':$1');\n  }\n  \n  extractParameters(path, parameters) {\n    const pathParams = path.match(\u002F{([^}]+)}\u002Fg) || [];\n    const pathParamNames = pathParams.map(p => p.slice(1, -1));\n    \n    return [\n      ...pathParamNames.map(name => ({\n        name,\n        in: 'path',\n        required: true,\n        schema: { type: 'string' }\n      })),\n      ...parameters\n    ];\n  }\n  \n  generateMarkdown() {\n    let md = `# API Reference\\n\\n`;\n    md += `**Base URL:** \\`${this.baseUrl}\\`\\n`;\n    md += `**Version:** ${this.version}\\n\\n`;\n    \n    const grouped = this.groupByTags();\n    \n    for (const [tag, endpoints] of Object.entries(grouped)) {\n      md += `## ${tag}\\n\\n`;\n      \n      for (const endpoint of endpoints) {\n        md += this.formatEndpointMarkdown(endpoint);\n      }\n    }\n    \n    return md;\n  }\n  \n  formatEndpointMarkdown(endpoint) {\n    let md = `### ${endpoint.method} ${endpoint.path}\\n\\n`;\n    md += `**Summary:** ${endpoint.summary}\\n\\n`;\n    \n    if (endpoint.description) {\n      md += `${endpoint.description}\\n\\n`;\n    }\n    \n    if (endpoint.parameters.length > 0) {\n      md += `#### Parameters\\n\\n`;\n      md += `| Name | In | Type | Required | Description |\\n`;\n      md += `|------|-----|------|----------|-------------|\\n`;\n      \n      endpoint.parameters.forEach(p => {\n        md += `| ${p.name} | ${p.in} | ${p.schema?.type || 'string'} | ${p.required} | ${p.description || ''} |\\n`;\n      });\n      md += `\\n`;\n    }\n    \n    if (endpoint.examples.length > 0) {\n      md += `#### Examples\\n\\n`;\n      \n      endpoint.examples.forEach(ex => {\n        md += `**Request:**\\n`;\n        md += '```\\n';\n        md += `${endpoint.method} ${this.baseUrl}${endpoint.path}\\n`;\n        if (ex.body) {\n          md += `\\n${JSON.stringify(ex.body, null, 2)}\\n`;\n        }\n        md += '```\\n\\n';\n        \n        md += `**Response:**\\n`;\n        md += '```json\\n';\n        md += `${JSON.stringify(ex.response, null, 2)}\\n`;\n        md += '```\\n\\n';\n      });\n    }\n    \n    return md;\n  }\n  \n  generateOpenAPI() {\n    return {\n      openapi: '3.0.0',\n      info: {\n        title: 'API Documentation',\n        version: this.version\n      },\n      servers: [{ url: this.baseUrl }],\n      paths: this.buildOpenAPIPaths()\n    };\n  }\n  \n  buildOpenAPIPaths() {\n    const paths = {};\n    \n    this.endpoints.forEach(endpoint => {\n      const pathKey = endpoint.path.replace(\u002F:(\\w+)\u002Fg, '{$1}');\n      \n      if (!paths[pathKey]) paths[pathKey] = {};\n      paths[pathKey][endpoint.method.toLowerCase()] = {\n        summary: endpoint.summary,\n        description: endpoint.description,\n        tags: endpoint.tags,\n        parameters: endpoint.parameters,\n        requestBody: endpoint.requestBody,\n        responses: endpoint.responses\n      };\n    });\n    \n    return paths;\n  }\n  \n  groupByTags() {\n    const grouped = {};\n    \n    this.endpoints.forEach(endpoint => {\n      const tag = endpoint.tags[0] || 'General';\n      if (!grouped[tag]) grouped[tag] = [];\n      grouped[tag].push(endpoint);\n    });\n    \n    return grouped;\n  }\n}\n```\n\n### Documentation Linter\n```javascript\nclass DocLinter {\n  constructor() {\n    this.rules = [\n      { id: 'HEADING-ORDER', check: this.checkHeadingOrder.bind(this) },\n      { id: 'CODE-LANGUAGE', check: this.checkCodeLanguage.bind(this) },\n      { id: 'LINK-TEXT', check: this.checkLinkText.bind(this) },\n      { id: 'COMPLEXITY', check: this.checkComplexity.bind(this) },\n      { id: 'ACTIVE-VOICE', check: this.checkActiveVoice.bind(this) }\n    ];\n  }\n  \n  lint(content, filePath) {\n    const results = [];\n    const lines = content.split('\\n');\n    \n    this.rules.forEach(rule => {\n      const issues = rule.check(content, lines, filePath);\n      results.push(...issues);\n    });\n    \n    return {\n      file: filePath,\n      issues: results,\n      score: this.calculateScore(results)\n    };\n  }\n  \n  checkHeadingOrder(content, lines) {\n    const issues = [];\n    const headingLevels = [];\n    \n    lines.forEach((line, index) => {\n      const match = line.match(\u002F^(#{1,6})\\s\u002F);\n      if (match) {\n        const level = match[1].length;\n        if (level > headingLevels[headingLevels.length - 1] + 1) {\n          issues.push({\n            line: index + 1,\n            rule: 'HEADING-ORDER',\n            severity: 'warning',\n            message: `Heading level jumps from H${headingLevels[headingLevels.length - 1]} to H${level}`\n          });\n        }\n        headingLevels.push(level);\n      }\n    });\n    \n    return issues;\n  }\n  \n  checkCodeLanguage(content, lines) {\n    const issues = [];\n    const codeBlockRegex = \u002F```(\\w+)?\u002Fg;\n    let match;\n    \n    while ((match = codeBlockRegex.exec(content)) !== null) {\n      if (!match[1]) {\n        const lineNumber = content.substring(0, match.index).split('\\n').length;\n        issues.push({\n          line: lineNumber,\n          rule: 'CODE-LANGUAGE',\n          severity: 'suggestion',\n          message: 'Code block missing language identifier'\n        });\n      }\n    }\n    \n    return issues;\n  }\n  \n  checkComplexity(content, lines) {\n    const issues = [];\n    const sentences = content.split(\u002F[.!?]+\u002F);\n    const longSentences = sentences.filter(s => s.split(' ').length > 25);\n    \n    if (longSentences.length > sentences.length * 0.1) {\n      issues.push({\n        rule: 'COMPLEXITY',\n        severity: 'suggestion',\n        message: `${longSentences.length} sentences exceed 25 words. Consider breaking them up.`\n      });\n    }\n    \n    return issues;\n  }\n  \n  checkActiveVoice(content) {\n    const issues = [];\n    const passivePatterns = [\n      \u002F\\b(is|are|was|were|been|being)\\s+\\w+ed\\b\u002Fgi,\n      \u002F\\b(has|have|had)\\s+been\\s+\\w+ed\\b\u002Fgi\n    ];\n    \n    passivePatterns.forEach(pattern => {\n      const matches = content.match(pattern);\n      if (matches && matches.length > 3) {\n        issues.push({\n          rule: 'ACTIVE-VOICE',\n          severity: 'suggestion',\n          message: `Found ${matches.length} passive voice constructions. Consider active voice.`\n        });\n      }\n    });\n    \n    return issues;\n  }\n  \n  calculateScore(issues) {\n    const weights = { error: 1, warning: 0.5, suggestion: 0.25 };\n    const maxScore = 100;\n    \n    const penalty = issues.reduce(\n      (sum, issue) => sum + (weights[issue.severity] || 0.5) * 5,\n      0\n    );\n    \n    return Math.max(0, maxScore - penalty);\n  }\n}\n```\n\n### Version Changelog Generator\n```javascript\nclass ChangelogGenerator {\n  constructor() {\n    this.commits = [];\n    this.breakingChanges = [];\n    this.features = [];\n    this.fixes = [];\n    this.chores = [];\n  }\n  \n  addCommits(commits, options = {}) {\n    const parser = new CommitParser();\n    \n    commits.forEach(commit => {\n      const parsed = parser.parse(commit.message);\n      \n      if (parsed.breaking) {\n        this.breakingChanges.push({ commit, parsed });\n      } else if (parsed.type === 'feat') {\n        this.features.push({ commit, parsed });\n      } else if (parsed.type === 'fix') {\n        this.fixes.push({ commit, parsed });\n      } else {\n        this.chores.push({ commit, parsed });\n      }\n    });\n    \n    return this;\n  }\n  \n  generateMarkdown(options = {}) {\n    const version = options.version || this.generateVersion();\n    const date = options.date || new Date().toISOString().split('T')[0];\n    \n    let md = `# Changelog\\n\\n`;\n    md += `## [${version}] - ${date}\\n\\n`;\n    \n    if (this.breakingChanges.length > 0) {\n      md += `### Breaking Changes\\n\\n`;\n      this.breakingChanges.forEach(({ parsed }) => {\n        md += `- ${parsed.description}`;\n        if (parsed.breakingDescription) {\n          md += `: ${parsed.breakingDescription}`;\n        }\n        md += '\\n';\n      });\n      md += '\\n';\n    }\n    \n    if (this.features.length > 0) {\n      md += `### New Features\\n\\n`;\n      this.features.forEach(({ parsed }) => {\n        md += `- ${parsed.description}\\n`;\n      });\n      md += '\\n';\n    }\n    \n    if (this.fixes.length > 0) {\n      md += `### Bug Fixes\\n\\n`;\n      this.fixes.forEach(({ parsed }) => {\n        md += `- ${parsed.description}\\n`;\n      });\n      md += '\\n';\n    }\n    \n    if (this.chores.length > 0) {\n      md += `### Maintenance\\n\\n`;\n      this.chores.forEach(({ parsed }) => {\n        md += `- ${parsed.description}\\n`;\n      });\n    }\n    \n    return md;\n  }\n  \n  generateVersion() {\n    const hasBreaking = this.breakingChanges.length > 0;\n    const hasFeatures = this.features.length > 0;\n    const hasFixes = this.fixes.length > 0;\n    \n    const parts = [0, 0, 0];\n    \n    if (hasBreaking) {\n      parts[0]++;\n      parts[1] = 0;\n      parts[2] = 0;\n    } else if (hasFeatures) {\n      parts[1]++;\n      parts[2] = 0;\n    } else if (hasFixes) {\n      parts[2]++;\n    }\n    \n    return parts.join('.');\n  }\n}\n\nclass CommitParser {\n  parse(message) {\n    const conventionalRegex = \u002F^(\\w+)(?:\\(([^)]+)\\))?!?:\\s+(.+)$\u002F;\n    const match = message.match(conventionalRegex);\n    \n    if (!match) {\n      return { type: 'other', description: message, breaking: false };\n    }\n    \n    const [, type, scope, description] = match;\n    const breaking = description.includes('BREAKING CHANGE');\n    \n    const breakingDescription = breaking \n      ? description.split('BREAKING CHANGE:')[1]?.trim()\n      : null;\n    \n    return {\n      type,\n      scope,\n      description: description.split('BREAKING CHANGE:')[0].trim(),\n      breaking,\n      breakingDescription\n    };\n  }\n}\n```\n\n## Best Practices\n\n1. Know your audience and write to their level\n2. Use active voice and present tense\n3. Write clear, concise sentences (under 25 words)\n4. Use consistent terminology throughout\n5. Provide context and examples for complex concepts\n6. Structure documents with clear hierarchy\n7. Keep code examples working and tested\n8. Use tables for structured information\n9. Include troubleshooting and common errors sections\n10. Review and edit documentation regularly\n",{"name":5,"license":14,"audience":38,"category":39,"description":7,"compatibility":40},"developers, technical writers, product managers","interdisciplinary","universal","main","5f7a3e453c98261f93d19a6a7de44ad8f7b72d41","interdisciplinary\u002Ftechnical-writing",2,"https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk","2026-03-01 06:55:40",{"source":8,"skillName":5,"commands":49,"prompt":59,"zipAvailable":60},[50,53,56],{"tool":51,"command":52},"npx","npx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill \"Technical Writing\"",{"tool":54,"command":55},"bunx","bunx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill \"Technical Writing\"",{"tool":57,"command":58},"pnpm","pnpm dlx skills add ffsshhttiikk\u002Fopencode-agents-skills --skill \"Technical Writing\"","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Finterdisciplinary\u002Ftechnical-writing\u002FSKILL.md",true,[],[63,75,85,94,104,113],{"skillNo":64,"name":65,"nameZh":6,"description":66,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":69,"publishedAt":33},"01M6CXT9TXDF79BWJTMQAPB49G","xamarin","Xamarin cross-platform development","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fxamarin\u002FSKILL.md",[],[65,70,71,72,73,74],"platform","cross-platform","mobile","opencode","development",{"skillNo":76,"name":77,"nameZh":6,"description":78,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":79,"categories":80,"keywords":81,"publishedAt":33},"01M6CXT8AD04MFYDA32VW3FKAG","wrangling","Data wrangling fundamentals","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwrangling\u002FSKILL.md",[],[77,82,73,83,84],"data wrangling","science","data",{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":91,"publishedAt":33},"01M6CXT717GCN7SRRNPBDP4508","windows","Windows operating system administration and scripting","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwindows\u002FSKILL.md",[],[87,92,73,93],"system","scripting",{"skillNo":95,"name":96,"nameZh":6,"description":97,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":100,"publishedAt":33},"01M6CXT54K794NMEM3XAPZZDKT","wide-column","Wide-column data model","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column\u002FSKILL.md",[],[101,102,73,103,84],"wide column","databases","model",{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":33},"01M6CXT28FMT2P4V9GZE67WA8E","wide-column-stores","Wide-column database stores","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwide-column-stores\u002FSKILL.md",[],[111,102,73,112],"wide column stores","database",{"skillNo":114,"name":115,"nameZh":6,"description":116,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":117,"categories":118,"keywords":119,"publishedAt":33},"01M6CXT0D0M0Q4T2N7C2RX7DSN","websockets","WebSocket implementation and best practices","https:\u002F\u002Fgithub.com\u002Fffsshhttiikk\u002Fopencode-agents-skills\u002Fblob\u002Fmain\u002Fwebsockets\u002FSKILL.md",[],[115,120,121,122,73,123,124],"websocket","real-time","time","best practices","implementation"]