diff --git a/.claude/skills/km-review.md b/.claude/skills/km-review.md new file mode 100644 index 00000000..a1bb99ee --- /dev/null +++ b/.claude/skills/km-review.md @@ -0,0 +1,332 @@ +# KM Documentation Review + +Applies comprehensive Knowledge Management (KM) team standards to README and documentation files, providing structured feedback and optional auto-fixes. + +## Usage + +``` +/km-review +/km-review --fix +/km-review --score-only +/km-review --ai-only (skip docs-linter, AI review only) +``` + +## Description + +This skill performs a comprehensive KM standards review on documentation files, checking for: +- Heading capitalization (Chicago title case for all headings H1–H6) +- Punctuation: Oxford comma in lists of three or more items; em dashes (—) without surrounding spaces for parenthetical phrases; en dashes (–) for ranges; no spaced em dashes ( — ) +- List formatting consistency (dashes vs asterisks) +- Link phrasing ("see" vs "refer to", "about" vs "around") +- Code block formatting (no $ prompts, proper language tags) +- SAP terminology consistency (on-premise, SAP BTP, etc.) +- Document structure and completeness +- Technical accuracy and clarity + +## Instructions + +When this skill is invoked: + +1. **Parse the command arguments**: + - Extract file path (required) + - Check for `--fix` flag (optional: apply safe fixes automatically) + - Check for `--score-only` flag (optional: return only quality score) + - Check for `--ai-only` flag (optional: skip docs-linter, use AI review only) + +2. **Validate the file**: + - Verify the file exists + - Confirm it's a markdown file (.md extension) + - Read the file content + +3. **Load KM standards context**: + - Read `prompts/km-doc-review.md` for the complete review prompt + - Read `docs/km-style-guide.md` for standards reference + - Read `training-data/km-feedback-patterns.json` if available + +4. **Optional: Run docs-linter first** (unless --ai-only flag is provided): + - Run: `node docs-linter/src/cli.js validate [FILE] --json` + - If successful, collect linter findings for merging with AI review + - If linter fails, continue with AI-only review + - Note: docs-linter is now fully ESM-compatible and operational + +5. **Spawn a Task agent** with `subagent_type: "general-purpose"` and pass this prompt: + +``` +You are a senior technical documentation reviewer for the SAP Knowledge Management (KM) team. Your task is to review the following markdown file against established KM documentation standards. + +## File to Review + +File path: [FILE_PATH] + +Content: +```markdown +[FILE_CONTENT] +``` + +## KM Standards to Apply + +[INSERT FULL CONTENT FROM prompts/km-doc-review.md HERE] + +## Additional Context from KM Style Guide + +[INSERT KEY SECTIONS FROM docs/km-style-guide.md HERE] + +## Docs-Linter Findings (if available) + +[IF docs-linter was run successfully, INSERT ITS FINDINGS HERE with this format:] + +The automated docs-linter has already identified the following objective issues: + +```json +[LINTER_FINDINGS_JSON] +``` + +Your task is to: +1. Validate and enhance these linter findings with context +2. Identify additional issues the linter may have missed +3. Provide comprehensive explanations for why each issue matters +4. Add any subjective/contextual issues that require human judgment + +## Review Instructions + +1. **Analyze the document** against all KM standards +2. **Categorize findings** by: + - Structural issues (heading hierarchy, section ordering, TOC) + - Formatting issues (lists, code blocks, links) + - Content issues (clarity, terminology, completeness) + - Technical issues (accuracy, examples, working links) + +3. **For each finding, provide**: + - Severity: critical | major | minor | info + - Category: structural | formatting | content | technical + - Line number (if applicable) + - Current text (exact quote) + - Suggested fix (exact replacement) + - Explanation (why this matters per KM standards) + - Is it safe to auto-fix? (boolean) + +4. **Calculate a quality score** (0-100): + - Start at 100 + - Deduct points based on severity and frequency + - Use the scoring rules from the KM standards + +5. **Provide recommendations**: + - Prioritized list of improvements + - References to KM style guide sections + - Examples from quality documentation + +## Output Format + +Return your review as structured JSON: + +```json +{ + "file": "[FILE_PATH]", + "qualityScore": { + "overall": 85, + "breakdown": { + "structure": 9, + "clarity": 8, + "maintainability": 9, + "developerUsability": 8 + }, + "rationale": "Brief explanation of score" + }, + "findings": [ + { + "severity": "major", + "category": "formatting", + "line": 42, + "current": "### Cloud Connector Configuration", + "suggested": "### Cloud Connector Configuration", + "explanation": "All headings (H1–H6) must use Chicago title case per KM standards. 'Configuration' is a major word and should be capitalized.", + "safeToAutoFix": true + } + ], + "summary": { + "total": 12, + "critical": 0, + "major": 4, + "minor": 6, + "info": 2, + "autoFixable": 8 + }, + "recommendations": [ + "Review heading capitalization rules in KM style guide", + "Consider updating Table of Contents to use dashes instead of asterisks" + ] +} +``` +``` + +5. **Process the agent's response**: + - Parse the JSON output + - If docs-linter was used, merge and deduplicate findings + - If `--score-only` flag: Display only the quality score and exit + - Otherwise: Display formatted findings + +6. **If `--fix` flag is provided**: + - Filter findings where `safeToAutoFix: true` + - Apply fixes to the file using the Edit tool + - Create a backup comment showing what was changed + - Display summary of applied fixes + +7. **Format the output** for the user: + +``` +📊 KM Documentation Review: [FILE_NAME] + +Quality Score: [SCORE]/100 +├─ Structure: [SCORE]/10 +├─ Clarity: [SCORE]/10 +├─ Maintainability: [SCORE]/10 +└─ Developer Usability: [SCORE]/10 + +Findings: [TOTAL] ([CRITICAL] critical, [MAJOR] major, [MINOR] minor, [INFO] info) + +[If --fix applied:] +✅ Auto-fixed [COUNT] issues + +Critical Issues: +[List critical findings with line numbers and explanations] + +Major Issues: +[List major findings] + +Minor Issues: +[List minor findings] + +Recommendations: +- [Recommendation 1] +- [Recommendation 2] + +[If not --fix:] +To automatically fix safe issues, run: /km-review [FILE_PATH] --fix +``` + +## Examples + +### Example 1: Basic Review + +**Input:** +``` +/km-review misc/onpremise/README.md +``` + +**Output:** +``` +📊 KM Documentation Review: misc/onpremise/README.md + +Quality Score: 88/100 +├─ Structure: 9/10 +├─ Clarity: 9/10 +├─ Maintainability: 9/10 +└─ Developer Usability: 8/10 + +Findings: 5 (0 critical, 2 major, 3 minor, 0 info) + +Major Issues: + +Line 86: Heading capitalization + Current: ### Cloud Connector Configuration + Suggested: ### Cloud Connector Configuration + → All headings (H1–H6) use Chicago title case — this heading is already correct + +Line 90: Heading capitalization + Current: ### SAP BTP destination + Suggested: ### SAP BTP Destination + → All headings must use Chicago title case; 'Destination' is a major word and must be capitalized + +Minor Issues: + +Line 7: Table of Contents formatting + Current: * [Overview](#overview) + Suggested: - [Overview](#overview) + → Use dashes (-) instead of asterisks (*) for list markers + +[... more findings ...] + +Recommendations: +- Review heading capitalization rules in docs/km-style-guide.md +- Update Table of Contents to use consistent dash markers +- All findings are safe to auto-fix + +To automatically fix these issues, run: /km-review misc/onpremise/README.md --fix +``` + +### Example 2: With Auto-Fix + +**Input:** +``` +/km-review misc/destinations/README.md --fix +``` + +**Output:** +``` +📊 KM Documentation Review: misc/destinations/README.md + +Quality Score: 92/100 (before fixes) + +Findings: 8 (0 critical, 2 major, 6 minor, 0 info) + +✅ Auto-fixed 8 issues: + ✓ Fixed 2 heading capitalization issues + ✓ Fixed 4 "refer to" → "see" replacements + ✓ Fixed 1 "around" → "about" replacement + ✓ Fixed 1 Table of Contents format + +Changes applied to misc/destinations/README.md + +New Quality Score: 98/100 +└─ All auto-fixable issues resolved + +Remaining Issues: None + +The file has been updated with KM standards. Review the changes with: + git diff misc/destinations/README.md +``` + +### Example 3: Score Only + +**Input:** +``` +/km-review misc/sslcerts/README.md --score-only +``` + +**Output:** +``` +📊 KM Quality Score: misc/sslcerts/README.md + +Overall: 95/100 +├─ Structure: 10/10 +├─ Clarity: 9/10 +├─ Maintainability: 10/10 +└─ Developer Usability: 9/10 + +Status: ✅ Excellent - Meets KM standards + +For detailed findings, run: /km-review misc/sslcerts/README.md +``` + +## Error Handling + +- If file doesn't exist: "Error: File not found: [FILE_PATH]" +- If not a markdown file: "Error: Only markdown files (.md) are supported" +- If agent fails: "Error: KM review agent failed. Please try again or review manually." +- If --fix fails: "Error: Failed to apply fixes. File unchanged. Findings available above." + +## Implementation Notes + +1. **Performance**: The review agent may take 10-20 seconds for large files +2. **Safety**: Only apply auto-fixes where `safeToAutoFix: true` +3. **Context**: The agent has full access to KM standards and training data +4. **Scoring**: Quality scores are calibrated against repository quality examples +5. **Git-Friendly**: Applied fixes generate clean diffs suitable for commits + +## Related Files + +- `prompts/km-doc-review.md` - Full KM review prompt (v2.0) +- `docs/km-style-guide.md` - Complete KM standards reference +- `docs/km-pr-checklist.md` - PR review checklist +- `training-data/km-feedback-patterns.json` - Feedback pattern analysis +- `.claude/skills/customer-tone.md` - Related skill for customer communications diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 143f9902..d5a04a71 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,3 +8,22 @@ - [ ] Sample tested and runs without errors - [ ] No sensitive data included +- [ ] Documentation reviewed and validated (if README changes included) + +## Documentation Validation + +If your PR includes changes to README.md files, you can validate them locally before submitting: + +```bash +# Check a specific README for issues +node docs-linter/src/cli.js check path/to/README.md + +# Get quality score +node docs-linter/src/cli.js validate path/to/README.md + +# Auto-fix safe issues +node docs-linter/src/cli.js fix path/to/README.md +``` + +The docs-linter checks for KM documentation standards including heading capitalization, list formatting, link phrasing, and technical accuracy. + diff --git a/.gitignore b/.gitignore index e1e46069..0f2e2e90 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Claude Code .claude/ +!.claude/skills/ # Dependency directories node_modules/ diff --git a/cap/README.md b/cap/README.md index 88fe403f..153cf22c 100644 --- a/cap/README.md +++ b/cap/README.md @@ -23,4 +23,4 @@ All CAP projects were generated using the steps outlined in this [blog post](htt For more information on integrating CI/CD into your CAP deployment strategy, please refer to the [Continuous Integration and Delivery YouTube Tutorial](https://www.youtube.com/watch?v=gvWSHSZFPok). ### License -Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file. +Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../LICENSES/Apache-2.0.txt) file. diff --git a/docs-linter/README.md b/docs-linter/README.md new file mode 100644 index 00000000..474b230d --- /dev/null +++ b/docs-linter/README.md @@ -0,0 +1,210 @@ +# KM Documentation Linter + +Automated linting system for applying SAP Knowledge Management (KM) team documentation standards to markdown files. + +## Overview + +The KM Documentation Linter is a Node.js-based tool that automatically checks and fixes common documentation issues based on patterns extracted from 30+ commits of KM team feedback. It works in conjunction with the `/km-review` Claude Code skill for comprehensive documentation quality assurance. + +## Architecture + +### Rule Categories + +1. **Structural Rules** (`src/rules/structural.js`) + - Required sections validation + - Heading hierarchy checks + - Table of contents validation + - Section ordering recommendations + - Document length and structure balance + +2. **Formatting Rules** (`src/rules/formatting.js`) + - Heading capitalization (Title case for H1/H2, Sentence case for H3+) + - List marker consistency (dashes vs asterisks) + - Link phrasing ("see" vs "refer to", "about" vs "around") + - Code block formatting (language tags, fence format) + - Punctuation and spacing consistency + +3. **Content Rules** (`src/rules/content.js`) + - SAP terminology consistency (on-premise, SAP BTP, etc.) + - Placeholder text detection ([TODO], [TBD]) + - Duplicate content identification + - Clarity and readability checks + - Complete list validation + +4. **Technical Rules** (`src/rules/technical.js`) + - Code syntax validation + - Link validation (working URLs, proper protocols) + - Version compatibility checks + - Example completeness + - Configuration accuracy + +### Training Data + +The linter learns from extracted KM feedback patterns stored in: +- `../training-data/km-feedback-patterns.json` - Analyzed patterns from commit feedback +- `../training-data/correction-dictionary.json` - Common corrections and typos +- `../training-data/quality-examples.json` - High-quality documentation examples + +## Commands + +### Check +Analyze a file for KM standards violations: +```bash +node src/cli.js check +node src/cli.js check --json +node src/cli.js check --comprehensive +``` + +### Fix +Automatically fix issues: +```bash +node src/cli.js fix +node src/cli.js fix --dry-run +node src/cli.js fix --safe-only +``` + +### Validate +Get quality score and recommendations: +```bash +node src/cli.js validate +node src/cli.js validate --json +``` + +## Current Status + +### ✅ Implemented +- Complete rule implementation (structural, formatting, content, technical) +- CLI with check, fix, and validate commands +- Training data integration +- Auto-fix capabilities with safety flags +- Quality scoring system +- JSON output support +- **Full ESM compatibility** ✨ + +### 🔮 Planned +- Pre-commit hook integration +- CI/CD pipeline integration +- Watch mode for live feedback +- VS Code extension +- GitHub Action +- Custom rule configuration + +## Integration with /km-review + +The linter is designed to work with the `/km-review` Claude Code skill: + +1. **Fast automated checks**: docs-linter runs first for objective rule violations +2. **Deep contextual review**: /km-review provides AI-powered analysis +3. **Combined output**: Merged findings with deduplication +4. **Hybrid fixes**: Safe auto-fixes + contextual improvements + +## Usage in Development + +### Pre-commit Hook +```bash +# Will be configured to run automatically +docs-linter check $STAGED_MD_FILES +``` + +### CI/CD Pipeline +```yaml +- name: Lint Documentation + run: | + find . -name "*.md" -not -path "*/node_modules/*" | \ + xargs -I {} node docs-linter/src/cli.js check {} --json +``` + +### Manual Review +```bash +# Check all README files +find . -name "README.md" -not -path "*/node_modules/*" | \ + xargs -I {} node src/cli.js validate {} +``` + +## Output Examples + +### Check Output +``` +📊 Checking onpremise/README.md... + +Results: + Total issues: 12 + Errors: 0 + Warnings: 5 + Info: 7 + Auto-fixable: 10 + +FORMATTING (8): + ⚠ Heading capitalization (line 86) + → H3+ headings should use sentence case + ⚠ Heading capitalization (line 90) + → H3+ headings should use sentence case + ... + +10 issues can be fixed automatically with: docs-linter fix onpremise/README.md +``` + +### Validate Output +``` +📊 Quality Score: onpremise/README.md + +Overall: 88/100 +├─ Structure: 9/10 +├─ Clarity: 9/10 +├─ Maintainability: 9/10 +└─ Developer Usability: 8/10 + +✓ Excellent - Meets KM standards +``` + +## Configuration + +The linter uses configuration from: +- KM Style Guide (`../docs/km-style-guide.md`) +- Training data patterns +- Quality example analysis + +Custom rules can be added in `src/rules/` following the existing pattern. + +## Development + +### Adding a New Rule + +1. Add to appropriate rule file (`structural.js`, `formatting.js`, etc.) +2. Follow the rule structure: +```javascript +checkRuleName(context) { + const issues = []; + // ... rule logic + issues.push({ + id: 'unique-rule-id', + category: 'structural|formatting|content|technical', + severity: 'error|warning|info', + message: 'Issue description', + line: lineNumber, + suggestion: 'How to fix', + fixable: true|false, + safeFix: true|false, + fix: { + type: 'replace', + from: 'old text', + to: 'new text' + } + }); + return issues; +} +``` + +3. Add to `this.ruleSet` in constructor +4. Test with sample files + +## Related Files + +- `.claude/skills/km-review.md` - AI-powered review skill +- `../docs/km-style-guide.md` - KM standards reference +- `../prompts/km-doc-review.md` - Comprehensive review prompt +- `../training-data/` - Extracted feedback patterns + +## License + +Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0. diff --git a/docs-linter/package-lock.json b/docs-linter/package-lock.json new file mode 100644 index 00000000..50875937 --- /dev/null +++ b/docs-linter/package-lock.json @@ -0,0 +1,1397 @@ +{ + "name": "docs-linter", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docs-linter", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "chalk": "4.1.2", + "commander": "^11.1.0", + "glob": "^10.3.10", + "js-yaml": "^4.1.0", + "remark": "^15.0.1", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "bin": { + "docs-linter": "src/cli.js" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://int.repositories.cloud.sap/artifactory/api/npm/build-milestones-npm/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://int.repositories.cloud.sap/artifactory/api/npm/build-milestones-npm/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://int.repositories.cloud.sap/artifactory/api/npm/build-milestones-npm/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://int.repositories.cloud.sap/artifactory/api/npm/build-milestones-npm/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs-linter/package.json b/docs-linter/package.json new file mode 100644 index 00000000..00b1b572 --- /dev/null +++ b/docs-linter/package.json @@ -0,0 +1,41 @@ +{ + "name": "docs-linter", + "version": "1.0.0", + "type": "module", + "description": "KM Feedback Training System - Documentation linter that learns from Knowledge Management team feedback patterns", + "main": "src/cli.js", + "bin": { + "docs-linter": "./src/cli.js" + }, + "engines": { + "node": ">=14.13.0" + }, + "scripts": { + "check": "node src/cli.js check", + "fix": "node src/cli.js fix", + "validate": "node src/cli.js validate", + "template": "node src/cli.js template", + "test": "node --test test/*.test.js" + }, + "dependencies": { + "chalk": "4.1.2", + "commander": "^11.1.0", + "glob": "^10.3.10", + "js-yaml": "^4.1.0", + "remark": "^15.0.1", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "keywords": [ + "documentation", + "linting", + "markdown", + "km", + "feedback", + "training" + ], + "author": "KM Feedback Training System", + "license": "Apache-2.0" +} diff --git a/docs-linter/rules/formatting.json b/docs-linter/rules/formatting.json new file mode 100644 index 00000000..95709f08 --- /dev/null +++ b/docs-linter/rules/formatting.json @@ -0,0 +1,135 @@ +{ + "headings": { + "titleCase": { + "applyTo": ["h1", "h2", "h3", "h4", "h5", "h6"], + "style": "chicago", + "description": "Chicago title case: capitalize all major words; lowercase articles (a, an, the), short prepositions (in, of, on, for, to, at, by, up), and coordinating conjunctions (and, but, or, nor, yet, so) unless first or last word", + "severity": "warning", + "message": "Use Chicago-style title case for all headings" + }, + "noTrailingQuestionMark": { + "severity": "warning", + "message": "Headings must not end with a question mark" + }, + "tableOfContentsLabel": { + "correct": "Table of Contents", + "severity": "info", + "message": "Use 'Table of Contents' (title case) as the ToC label" + }, + "corrections": { + "Support ticket checklist": "Checklist for Support Tickets", + "Common causes for deployment errors": "Common Causes for Deployment Errors", + "Screenshots Required": "Provide Screenshots", + "Deployment Additional Resources": "Additional Resources for Deployment" + } + }, + "lists": { + "preferredMarker": "-", + "applyToTableOfContents": true, + "consistency": { + "severity": "warning", + "message": "Use consistent bullet markers throughout the document, including the Table of Contents" + } + }, + "links": { + "contextImprovements": { + "For more information around": "For more information about", + "refer to this": "see this", + " via ": " using " + }, + "preferHttps": true, + "descriptiveText": { + "severity": "info", + "message": "Consider using descriptive text instead of bare URLs" + } + }, + "codeBlocks": { + "requireLanguage": { + "minLength": 20, + "severity": "info", + "message": "Consider specifying language for code blocks" + }, + "fenceFormat": { + "correct": "```", + "incorrect": "````", + "severity": "warning" + }, + "noPrompts": { + "severity": "info", + "message": "Remove $ prompt from commands for better copy-paste experience" + } + }, + "terminology": { + "replacements": { + "backend": "back-end", + "back end": "back-end", + "via Cloud Connector": "using Cloud Connector", + " is working": " works", + " is functioning": " works" + }, + "errorCodePairs": { + "pattern": "HTTP \\d+/\\d+", + "severity": "info", + "message": "Spell out 'and' between HTTP error codes instead of using a slash (e.g. 'HTTP 401 and HTTP 403')" + } + }, + "listItems": { + "emDashSeparator": { + "pattern": "^[*-]\\s+`.+`\\s+[—–]\\s+", + "severity": "warning", + "message": "Use a colon instead of an em dash to separate a term from its description (e.g. '`key`: description')" + }, + "arrowSeparator": { + "pattern": "^[*-]\\s+.+\\s+→\\s+", + "severity": "warning", + "message": "Use a colon instead of an arrow to separate a term from its description (e.g. '- term: description')" + } + }, + "spacing": { + "multipleSpaces": { + "severity": "info", + "message": "Use single spaces between words" + }, + "trailingSpaces": { + "severity": "info", + "message": "Remove trailing spaces" + } + }, + "punctuation": { + "oxfordComma": { + "severity": "warning", + "message": "Use the Oxford comma in lists of three or more items (e.g. 'red, white, and blue')" + }, + "emDash": { + "severity": "warning", + "usage": "parenthetical", + "format": "—", + "noSpaces": true, + "message": "Use an em dash (—) without surrounding spaces for parenthetical phrases (e.g. 'The service—when configured correctly—returns a token'). Do not use a hyphen (-) or spaced em dash ( — ) as a substitute." + }, + "enDash": { + "severity": "info", + "usage": "ranges", + "format": "–", + "message": "Use an en dash (–) for ranges (e.g. 'pages 10–20', 'January–March'). Do not use a hyphen for ranges." + }, + "spacedEmDash": { + "pattern": " — ", + "severity": "warning", + "message": "Remove spaces around em dashes. Chicago style uses unspaced em dashes (e.g. 'the service—not the destination—handles this')." + } + }, + "numbers": { + "spellOut": { + "range": [1, 10], + "severity": "info", + "message": "Spell out numbers one through ten in prose (e.g. 'three steps', not '3 steps')" + }, + "useNumerals": { + "threshold": 11, + "severity": "info", + "message": "Use numerals for numbers 11 and above (e.g. '12 files', not 'twelve files')" + }, + "exceptions": ["version numbers", "code values", "measurements", "percentages", "ranges"] + } +} \ No newline at end of file diff --git a/docs-linter/rules/structural.json b/docs-linter/rules/structural.json new file mode 100644 index 00000000..3612f623 --- /dev/null +++ b/docs-linter/rules/structural.json @@ -0,0 +1,46 @@ +{ + "requiredSections": { + "readme": [ + { + "name": "overview", + "alternatives": ["introduction", "about"], + "severity": "error", + "message": "README files must include an overview or introduction section" + }, + { + "name": "prerequisites", + "alternatives": ["requirements"], + "severity": "error", + "message": "README files must include a prerequisites or requirements section" + } + ], + "technicalGuide": [ + { + "name": "troubleshooting", + "alternatives": ["known issues", "issues", "problems"], + "severity": "warning", + "message": "Technical guides should include troubleshooting or known issues section" + } + ] + }, + "sectionOrder": [ + "overview", + "prerequisites", + "getting started", + "configuration", + "usage", + "troubleshooting", + "additional resources", + "license" + ], + "headingHierarchy": { + "maxH1Count": 1, + "allowSkippedLevels": false, + "severity": "error" + }, + "tableOfContents": { + "requireForLength": 10000, + "requireForHeadings": 8, + "severity": "info" + } +} \ No newline at end of file diff --git a/docs-linter/src/cli.js b/docs-linter/src/cli.js new file mode 100755 index 00000000..b3954f4c --- /dev/null +++ b/docs-linter/src/cli.js @@ -0,0 +1,298 @@ +#!/usr/bin/env node +/** + * KM Documentation Linter CLI + * Comprehensive markdown linting based on KM feedback patterns + */ + +import { program } from 'commander'; +import chalk from 'chalk'; +import path from 'path'; +import fs from 'fs'; +import DocsLinter from './linter.js'; + +// Initialize linter +const linter = new DocsLinter(); + +// CLI Configuration +program + .name('docs-linter') + .description('KM Documentation Linter - Apply Knowledge Management standards to markdown files') + .version('1.0.0'); + +// Check command +program + .command('check ') + .description('Check a file for KM standards violations') + .option('--json', 'Output results as JSON') + .option('--comprehensive', 'Run comprehensive checks (slower but more thorough)') + .action(async (file, options) => { + try { + const filePath = path.resolve(process.cwd(), file); + + if (!fs.existsSync(filePath)) { + console.error(chalk.red(`Error: File not found: ${filePath}`)); + process.exit(1); + } + + if (!filePath.endsWith('.md')) { + console.error(chalk.red('Error: Only markdown files (.md) are supported')); + process.exit(1); + } + + console.log(chalk.blue(`Checking ${path.basename(filePath)}...`)); + + const result = await linter.checkFile(filePath, { + comprehensive: options.comprehensive || false + }); + + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + } else { + displayCheckResults(result); + } + + // Exit with error code if critical issues found + const hasCritical = result.issues.some(i => i.severity === 'error'); + process.exit(hasCritical ? 1 : 0); + + } catch (error) { + console.error(chalk.red(`Error: ${error.message}`)); + process.exit(1); + } + }); + +// Fix command +program + .command('fix ') + .description('Fix issues in a file automatically') + .option('--dry-run', 'Show what would be fixed without applying changes') + .option('--safe-only', 'Only apply fixes marked as safe') + .action(async (file, options) => { + try { + const filePath = path.resolve(process.cwd(), file); + + if (!fs.existsSync(filePath)) { + console.error(chalk.red(`Error: File not found: ${filePath}`)); + process.exit(1); + } + + console.log(chalk.blue(`${options.dryRun ? 'Analyzing' : 'Fixing'} ${path.basename(filePath)}...`)); + + const result = await linter.fixFile(filePath, { + dryRun: options.dryRun || false, + safeOnly: options.safeOnly || false + }); + + if (options.dryRun) { + console.log(chalk.yellow('\nDry run - no changes applied\n')); + displayFixPreview(result); + } else { + displayFixResults(result); + } + + process.exit(0); + + } catch (error) { + console.error(chalk.red(`Error: ${error.message}`)); + process.exit(1); + } + }); + +// Validate command +program + .command('validate ') + .description('Validate a file and provide quality score') + .option('--json', 'Output results as JSON') + .action(async (file, options) => { + try { + const filePath = path.resolve(process.cwd(), file); + + if (!fs.existsSync(filePath)) { + console.error(chalk.red(`Error: File not found: ${filePath}`)); + process.exit(1); + } + + console.log(chalk.blue(`Validating ${path.basename(filePath)}...`)); + + const result = await linter.validateFile(filePath); + + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + } else { + displayValidationResults(result); + } + + process.exit(0); + + } catch (error) { + console.error(chalk.red(`Error: ${error.message}`)); + process.exit(1); + } + }); + +// Display functions + +function displayCheckResults(result) { + const { file, issues, summary } = result; + + console.log('\n' + chalk.bold('Results:')); + console.log(` Total issues: ${summary.total}`); + console.log(` ${chalk.red('Errors:')} ${summary.errors}`); + console.log(` ${chalk.yellow('Warnings:')} ${summary.warnings}`); + console.log(` ${chalk.blue('Info:')} ${summary.info}`); + console.log(` ${chalk.green('Auto-fixable:')} ${summary.fixable}`); + + if (summary.total === 0) { + console.log(chalk.green('\n✓ No issues found!')); + return; + } + + // Group by category + const byCategory = { + structural: issues.filter(i => i.category === 'structural'), + formatting: issues.filter(i => i.category === 'formatting'), + content: issues.filter(i => i.category === 'content'), + technical: issues.filter(i => i.category === 'technical') + }; + + Object.entries(byCategory).forEach(([category, categoryIssues]) => { + if (categoryIssues.length === 0) return; + + console.log(chalk.bold(`\n${category.toUpperCase()} (${categoryIssues.length}):`)); + + categoryIssues.slice(0, 10).forEach(issue => { + const icon = getSeverityIcon(issue.severity); + const line = issue.line ? chalk.gray(` (line ${issue.line})`) : ''; + console.log(` ${icon} ${issue.message}${line}`); + if (issue.suggestion) { + console.log(chalk.gray(` → ${issue.suggestion}`)); + } + }); + + if (categoryIssues.length > 10) { + console.log(chalk.gray(` ... and ${categoryIssues.length - 10} more`)); + } + }); + + if (summary.fixable > 0) { + console.log(chalk.green(`\n${summary.fixable} issues can be fixed automatically with: docs-linter fix ${path.basename(file)}`)); + } +} + +function displayFixResults(result) { + const { file, changes, applied } = result; + + if (changes.length === 0) { + console.log(chalk.green('\n✓ No fixable issues found!')); + return; + } + + console.log(chalk.bold(`\n${applied ? 'Applied' : 'Would apply'} ${changes.length} fixes:\n`)); + + const byType = {}; + changes.forEach(change => { + byType[change.type] = (byType[change.type] || 0) + 1; + }); + + Object.entries(byType).forEach(([type, count]) => { + console.log(` ${chalk.green('✓')} ${type}: ${count} fix${count > 1 ? 'es' : ''}`); + }); + + if (applied) { + console.log(chalk.green(`\n✓ Changes applied to ${path.basename(file)}`)); + console.log(chalk.gray(' Review with: git diff ' + path.basename(file))); + } +} + +function displayFixPreview(result) { + const { changes } = result; + + if (changes.length === 0) { + console.log(chalk.green('✓ No fixable issues found!')); + return; + } + + console.log(chalk.bold(`Would fix ${changes.length} issues:\n`)); + + changes.slice(0, 15).forEach(change => { + console.log(` ${chalk.yellow('~')} ${change.description}`); + }); + + if (changes.length > 15) { + console.log(chalk.gray(` ... and ${changes.length - 15} more`)); + } + + console.log(chalk.gray('\nRun without --dry-run to apply these fixes')); +} + +function displayValidationResults(result) { + const { score, feedback, recommendations } = result; + + console.log(chalk.bold('\n📊 Quality Score:\n')); + console.log(` Overall: ${getScoreColor(score.overall)}${score.overall}/100${chalk.reset()}`); + + if (score.breakdown) { + Object.entries(score.breakdown).forEach(([key, value]) => { + const label = key.replace(/([A-Z])/g, ' $1').trim(); + console.log(` ${label}: ${value}/10`); + }); + } + + if (score.rationale) { + console.log(chalk.gray(`\n ${score.rationale}`)); + } + + if (feedback && feedback.length > 0) { + console.log(chalk.bold('\nFeedback:\n')); + feedback.slice(0, 5).forEach(item => { + const icon = getSeverityIcon(item.type); + console.log(` ${icon} ${item.message}`); + }); + + if (feedback.length > 5) { + console.log(chalk.gray(` ... and ${feedback.length - 5} more items`)); + } + } + + if (recommendations && recommendations.length > 0) { + console.log(chalk.bold('\nRecommendations:\n')); + recommendations.forEach(rec => { + console.log(` ${chalk.blue('•')} ${rec}`); + }); + } + + // Status message + if (score.overall >= 90) { + console.log(chalk.green('\n✓ Excellent - Meets KM standards')); + } else if (score.overall >= 75) { + console.log(chalk.yellow('\n⚠ Good - Minor improvements suggested')); + } else if (score.overall >= 60) { + console.log(chalk.yellow('\n⚠ Fair - Review recommendations')); + } else { + console.log(chalk.red('\n✗ Needs improvement - Significant issues found')); + } +} + +function getSeverityIcon(severity) { + switch (severity) { + case 'error': return chalk.red('✗'); + case 'warning': return chalk.yellow('⚠'); + case 'info': return chalk.blue('ℹ'); + default: return chalk.gray('·'); + } +} + +function getScoreColor(score) { + if (score >= 90) return chalk.green; + if (score >= 75) return chalk.yellow; + if (score >= 60) return chalk.yellow; + return chalk.red; +} + +// Parse arguments +program.parse(process.argv); + +// Show help if no command provided +if (!process.argv.slice(2).length) { + program.outputHelp(); +} diff --git a/docs-linter/src/linter.js b/docs-linter/src/linter.js new file mode 100644 index 00000000..5a47917c --- /dev/null +++ b/docs-linter/src/linter.js @@ -0,0 +1,327 @@ +/** + * KM Documentation Linter - Core Linter Class + * + * This class implements the main linting logic, applying rules derived from + * KM feedback patterns to improve documentation quality. + */ + +import { readFileSync, writeFileSync, existsSync } from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import { visit } from 'unist-util-visit'; + +import StructuralRules from './rules/structural.js'; +import FormattingRules from './rules/formatting.js'; +import ContentRules from './rules/content.js'; +import TechnicalRules from './rules/technical.js'; + +// ESM equivalent of __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +class DocsLinter { + constructor() { + this.rules = { + structural: new StructuralRules(), + formatting: new FormattingRules(), + content: new ContentRules(), + technical: new TechnicalRules() + }; + + // Initialize remark processor for markdown parsing + this.processor = unified().use(remarkParse); + + this.loadTrainingData(); + } + + /** + * Load training data from extracted KM patterns + */ + loadTrainingData() { + try { + const trainingDataPath = path.resolve(__dirname, '../../training-data'); + + if (existsSync(path.join(trainingDataPath, 'km-feedback-patterns.json'))) { + const patterns = JSON.parse(readFileSync( + path.join(trainingDataPath, 'km-feedback-patterns.json'), + 'utf8' + )); + this.patterns = patterns; + } + + if (existsSync(path.join(trainingDataPath, 'correction-dictionary.json'))) { + const corrections = JSON.parse(readFileSync( + path.join(trainingDataPath, 'correction-dictionary.json'), + 'utf8' + )); + this.corrections = corrections; + } + + if (existsSync(path.join(trainingDataPath, 'quality-examples.json'))) { + const examples = JSON.parse(readFileSync( + path.join(trainingDataPath, 'quality-examples.json'), + 'utf8' + )); + this.qualityExamples = examples; + } + + } catch (error) { + console.warn('Warning: Could not load training data:', error.message); + } + } + + /** + * Check a file for issues based on KM feedback patterns + */ + async checkFile(filePath, options = {}) { + const content = readFileSync(filePath, 'utf8'); + const ast = this.processor.parse(content); + + const issues = []; + const context = { + file: filePath, + content, + ast, + patterns: this.patterns, + corrections: this.corrections, + options + }; + + // Apply all rule categories + issues.push(...await this.rules.structural.check(context)); + issues.push(...await this.rules.formatting.check(context)); + issues.push(...await this.rules.content.check(context)); + issues.push(...await this.rules.technical.check(context)); + + // Auto-fix safe issues if requested + if (options.autoFixSafe) { + const fixes = this.getSafeFixes(issues); + if (fixes.length > 0) { + await this.applyFixes(filePath, fixes); + issues.forEach(issue => { + if (fixes.some(fix => fix.issueId === issue.id)) { + issue.fixed = true; + } + }); + } + } + + return { + file: filePath, + issues: issues, + summary: this.generateSummary(issues) + }; + } + + /** + * Fix a file by applying corrections + */ + async fixFile(filePath, options = {}) { + const checkResult = await this.checkFile(filePath, options); + const fixes = options.safeOnly ? + this.getSafeFixes(checkResult.issues) : + this.getAllFixes(checkResult.issues); + + if (options.dryRun) { + return { + file: filePath, + changes: fixes, + applied: false + }; + } + + if (fixes.length > 0) { + await this.applyFixes(filePath, fixes); + } + + return { + file: filePath, + changes: fixes, + applied: true + }; + } + + /** + * Validate a file against KM standards + */ + async validateFile(filePath, options = {}) { + const checkResult = await this.checkFile(filePath, { comprehensive: true }); + const score = this.calculateQualityScore(checkResult.issues, filePath); + + return { + file: filePath, + score: score, + feedback: this.generateValidationFeedback(checkResult.issues), + recommendations: this.generateRecommendations(score, checkResult.issues) + }; + } + + /** + * Get fixes that are considered safe to apply automatically + */ + getSafeFixes(issues) { + return issues + .filter(issue => issue.fixable && issue.safeFix) + .map(issue => ({ + issueId: issue.id, + type: issue.type, + description: issue.message, + fix: issue.fix + })); + } + + /** + * Get all available fixes + */ + getAllFixes(issues) { + return issues + .filter(issue => issue.fixable) + .map(issue => ({ + issueId: issue.id, + type: issue.type, + description: issue.message, + fix: issue.fix + })); + } + + /** + * Apply fixes to a file + */ + async applyFixes(filePath, fixes) { + let content = readFileSync(filePath, 'utf8'); + + // Apply fixes in reverse order to maintain line numbers + fixes.sort((a, b) => (b.fix.line || 0) - (a.fix.line || 0)); + + for (const fix of fixes) { + if (fix.fix.type === 'replace') { + content = content.replace(fix.fix.from, fix.fix.to); + } else if (fix.fix.type === 'insertAfter') { + const lines = content.split('\n'); + lines.splice(fix.fix.line, 0, fix.fix.content); + content = lines.join('\n'); + } else if (fix.fix.type === 'removeLine') { + const lines = content.split('\n'); + lines.splice(fix.fix.line - 1, 1); + content = lines.join('\n'); + } + } + + writeFileSync(filePath, content); + } + + /** + * Calculate quality score based on issues found + */ + calculateQualityScore(issues, filePath) { + let score = 100; + + issues.forEach(issue => { + switch (issue.severity) { + case 'error': + score -= 10; + break; + case 'warning': + score -= 5; + break; + case 'info': + score -= 2; + break; + } + }); + + // Bonus points for following quality examples + if (this.qualityExamples) { + const isQualityExample = this.qualityExamples.some(example => + filePath.includes(example.file.replace('./', '')) + ); + if (isQualityExample) { + score += 10; + } + } + + return Math.max(0, Math.min(100, score)); + } + + /** + * Generate validation feedback + */ + generateValidationFeedback(issues) { + return issues.map(issue => ({ + type: issue.severity, + message: issue.message, + line: issue.line, + suggestion: issue.suggestion + })); + } + + /** + * Generate recommendations based on score and issues + */ + generateRecommendations(score, issues) { + const recommendations = []; + + if (score < 50) { + recommendations.push('Consider reviewing high-quality examples in the repository'); + } + + const errorCount = issues.filter(i => i.severity === 'error').length; + if (errorCount > 0) { + recommendations.push('Fix critical errors before submitting for review'); + } + + const formattingIssues = issues.filter(i => i.category === 'formatting').length; + if (formattingIssues > 3) { + recommendations.push('Review formatting standards in the KM style guide'); + } + + return recommendations; + } + + /** + * Generate summary of issues + */ + generateSummary(issues) { + const summary = { + total: issues.length, + errors: issues.filter(i => i.severity === 'error').length, + warnings: issues.filter(i => i.severity === 'warning').length, + info: issues.filter(i => i.severity === 'info').length, + fixable: issues.filter(i => i.fixable).length + }; + + summary.categories = { + structural: issues.filter(i => i.category === 'structural').length, + formatting: issues.filter(i => i.category === 'formatting').length, + content: issues.filter(i => i.category === 'content').length, + technical: issues.filter(i => i.category === 'technical').length + }; + + return summary; + } + + /** + * Extract text content from markdown AST + */ + extractText(ast) { + let text = ''; + visit(ast, (node) => { + if (node.type === 'text') { + text += node.value + ' '; + } + }); + return text.trim(); + } + + /** + * Get line number for a node in the AST + */ + getLineNumber(node) { + return node.position ? node.position.start.line : null; + } +} + +export default DocsLinter; \ No newline at end of file diff --git a/docs-linter/src/rules/content.js b/docs-linter/src/rules/content.js new file mode 100644 index 00000000..853ecde9 --- /dev/null +++ b/docs-linter/src/rules/content.js @@ -0,0 +1,457 @@ +/** + * Content Rules - Based on KM Feedback Patterns + * + * These rules focus on content quality, clarity, and completeness + * based on improvements identified in KM feedback patterns. + */ + +import { visit } from 'unist-util-visit'; + +class ContentRules { + constructor() { + this.ruleSet = [ + this.checkContentClarity, + this.checkCompleteness, + this.checkConsistency, + this.checkWritingStyle, + this.checkExamples + ]; + } + + /** + * Check all content rules against the document + */ + async check(context) { + const issues = []; + + for (const rule of this.ruleSet) { + const ruleIssues = await rule.call(this, context); + issues.push(...ruleIssues); + } + + return issues; + } + + /** + * Check for content clarity improvements based on KM patterns + */ + checkContentClarity(context) { + const issues = []; + const { content, patterns } = context; + + if (!patterns) return issues; + + const lines = content.split('\n'); + + // Check for clarity improvements from KM patterns + if (patterns.content) { + patterns.content.forEach(pattern => { + if (pattern.before && pattern.after) { + const beforeText = pattern.before.toLowerCase(); + + lines.forEach((line, index) => { + const lowerLine = line.toLowerCase(); + + // Check for vague language that was improved + if (lowerLine.includes(beforeText)) { + issues.push({ + id: `clarity-improvement-${index + 1}`, + category: 'content', + severity: 'info', + message: 'Content could be clearer based on KM feedback patterns', + line: index + 1, + suggestion: `Consider: "${pattern.after}"`, + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: pattern.before, + to: pattern.after + } + }); + } + }); + } + }); + } + + // Check for common clarity issues + lines.forEach((line, index) => { + const lowerLine = line.toLowerCase().trim(); + + // Vague language + const vaguePatterns = [ + { pattern: 'it is recommended', suggestion: 'Use specific recommendation' }, + { pattern: 'you can', suggestion: 'Be more specific about actions' }, + { pattern: 'some users', suggestion: 'Specify which users or scenarios' }, + { pattern: 'in some cases', suggestion: 'Specify the cases' }, + { pattern: 'might work', suggestion: 'Be definitive about outcomes' } + ]; + + vaguePatterns.forEach(vague => { + if (lowerLine.includes(vague.pattern)) { + issues.push({ + id: `vague-language-${index + 1}`, + category: 'content', + severity: 'info', + message: `Vague language: "${vague.pattern}"`, + line: index + 1, + suggestion: vague.suggestion, + fixable: false, + safeFix: false + }); + } + }); + + // Passive voice checks + if (this.isPassiveVoice(line)) { + issues.push({ + id: `passive-voice-${index + 1}`, + category: 'content', + severity: 'info', + message: 'Consider using active voice for clearer instructions', + line: index + 1, + suggestion: 'Rewrite in active voice', + fixable: false, + safeFix: false + }); + } + }); + + return issues; + } + + /** + * Check content completeness based on context + */ + checkCompleteness(context) { + const issues = []; + const { content } = context; + + // Check for placeholder text + const placeholderPatterns = [ + '[TODO]', + '[TBD]', + '[Add content here]', + '[Description]', + '[Insert]', + 'Lorem ipsum' + ]; + + const lines = content.split('\n'); + lines.forEach((line, index) => { + placeholderPatterns.forEach(placeholder => { + if (line.includes(placeholder)) { + issues.push({ + id: `placeholder-${index + 1}`, + category: 'content', + severity: 'error', + message: `Placeholder text found: "${placeholder}"`, + line: index + 1, + suggestion: 'Replace with actual content', + fixable: false, + safeFix: false + }); + } + }); + }); + + // Check for incomplete lists + const unfinishedSentences = [ + 'such as:', + 'including:', + 'for example:', + 'like:' + ]; + + lines.forEach((line, index) => { + unfinishedSentences.forEach(pattern => { + if (line.toLowerCase().endsWith(pattern)) { + const nextLine = lines[index + 1]; + if (!nextLine || (!nextLine.trim().startsWith('-') && !nextLine.trim().startsWith('*'))) { + issues.push({ + id: `incomplete-list-${index + 1}`, + category: 'content', + severity: 'warning', + message: `Incomplete list or examples after "${pattern}"`, + line: index + 1, + suggestion: 'Add list items or examples', + fixable: false, + safeFix: false + }); + } + } + }); + }); + + // Check for broken internal references + const internalLinks = content.match(/\[.*?\]\(#.*?\)/g) || []; + const headings = this.extractAnchorTargets(content); + + internalLinks.forEach(link => { + const match = link.match(/\[.*?\]\(#(.*?)\)/); + if (match) { + const anchor = match[1]; + if (!headings.includes(anchor)) { + issues.push({ + id: `broken-internal-link-${anchor}`, + category: 'content', + severity: 'error', + message: `Broken internal link: #${anchor}`, + suggestion: 'Fix the anchor link or add the missing heading', + fixable: false, + safeFix: false + }); + } + } + }); + + return issues; + } + + /** + * Check for terminology and style consistency + */ + checkConsistency(context) { + const issues = []; + const { content } = context; + + // Common terminology inconsistencies based on KM patterns + const terminologyChecks = [ + { + variations: ['onpremise', 'on premise', 'on-premise'], + preferred: 'on-premise', + message: 'Inconsistent terminology for on-premise' + }, + { + variations: ['BTP', 'Business Technology Platform', 'SAP BTP'], + preferred: 'SAP BTP', + message: 'Use consistent SAP BTP terminology' + }, + { + variations: ['Cloud Connector', 'cloud connector', 'SCC'], + preferred: 'Cloud Connector', + message: 'Use consistent Cloud Connector terminology' + } + ]; + + terminologyChecks.forEach(check => { + const foundVariations = new Set(); + const lines = content.split('\n'); + + lines.forEach((line, index) => { + check.variations.forEach(variation => { + if (line.includes(variation)) { + foundVariations.add(variation); + } + }); + }); + + if (foundVariations.size > 1) { + const variations = Array.from(foundVariations); + issues.push({ + id: `terminology-inconsistency-${check.preferred.replace(/\s/g, '-')}`, + category: 'content', + severity: 'warning', + message: check.message, + suggestion: `Use "${check.preferred}" consistently throughout the document`, + fixable: true, + safeFix: false, + fix: { + type: 'standardize-terminology', + variations: variations, + preferred: check.preferred + } + }); + } + }); + + return issues; + } + + /** + * Check writing style based on KM improvements + */ + checkWritingStyle(context) { + const issues = []; + const { content } = context; + + const lines = content.split('\n'); + + lines.forEach((line, index) => { + const lowerLine = line.toLowerCase(); + + // Check for improved phrasing from KM patterns + const styleImprovements = [ + { + pattern: 'for more information around', + improvement: 'for more information about', + message: 'Use "about" instead of "around"' + }, + { + pattern: 'refer to this', + improvement: 'see this', + message: 'Use "see" instead of "refer to" for more natural language' + }, + { + pattern: 'for these purposes', + improvement: 'for this purpose', + message: 'Use singular form for clarity' + } + ]; + + styleImprovements.forEach(style => { + if (lowerLine.includes(style.pattern)) { + issues.push({ + id: `style-improvement-${index + 1}`, + category: 'content', + severity: 'info', + message: style.message, + line: index + 1, + suggestion: `Use: "${style.improvement}"`, + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: style.pattern, + to: style.improvement + } + }); + } + }); + + // Check for proper sentence structure + if (line.trim().length > 0 && !line.trim().startsWith('#') && !line.trim().startsWith('-') && !line.trim().startsWith('*')) { + // Very long sentences (over 150 characters) might be hard to read + if (line.length > 150 && line.includes(',') && !line.includes('```')) { + issues.push({ + id: `long-sentence-${index + 1}`, + category: 'content', + severity: 'info', + message: 'Long sentence might be hard to read', + line: index + 1, + suggestion: 'Consider breaking into shorter sentences', + fixable: false, + safeFix: false + }); + } + } + }); + + return issues; + } + + /** + * Check for appropriate examples and code snippets + */ + checkExamples(context) { + const issues = []; + const { content, ast } = context; + + // Check that code blocks have explanations + visit(ast, 'code', (node) => { + const line = this.getLineNumber(node); + const code = node.value; + + if (code.length > 50) { // Substantial code blocks + // Look for explanation before or after the code block + const hasExplanation = this.hasNearbyExplanation(node, context.content); + + if (!hasExplanation) { + issues.push({ + id: `code-needs-explanation-${line}`, + category: 'content', + severity: 'info', + message: 'Code block should have explanation', + line: line, + suggestion: 'Add explanation before or after the code block', + fixable: false, + safeFix: false + }); + } + } + }); + + // Check for outdated year references + const currentYear = new Date().getFullYear(); + const lines = content.split('\n'); + + lines.forEach((line, index) => { + const yearMatch = line.match(/20\d{2}/g); + if (yearMatch) { + yearMatch.forEach(year => { + const yearNum = parseInt(year); + if (yearNum < currentYear - 1 && !line.includes('©') && !line.includes('since')) { + issues.push({ + id: `outdated-year-${index + 1}`, + category: 'content', + severity: 'info', + message: `Potentially outdated year reference: ${year}`, + line: index + 1, + suggestion: `Consider updating to ${currentYear}`, + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: year, + to: currentYear.toString() + } + }); + } + }); + } + }); + + return issues; + } + + // Utility methods + + isPassiveVoice(sentence) { + // Simple passive voice detection + const passiveIndicators = [ + 'is being', 'are being', 'was being', 'were being', + 'is done', 'are done', 'was done', 'were done', + 'is created', 'are created', 'was created', 'were created' + ]; + + const lowerSentence = sentence.toLowerCase(); + return passiveIndicators.some(indicator => lowerSentence.includes(indicator)); + } + + extractAnchorTargets(content) { + // Extract heading anchors from markdown + const headings = content.match(/^#+\s+(.+)$/gm) || []; + return headings.map(heading => { + const text = heading.replace(/^#+\s+/, ''); + return text.toLowerCase() + .replace(/[^\w\s-]/g, '') + .replace(/\s+/g, '-'); + }); + } + + hasNearbyExplanation(codeNode, content) { + // Simplified check - would need more sophisticated logic + const line = this.getLineNumber(codeNode); + if (!line) return false; + + const lines = content.split('\n'); + const beforeLine = lines[line - 2] || ''; + const afterLine = lines[line + 1] || ''; + + // Check if there's explanatory text nearby + return ( + beforeLine.length > 20 || + afterLine.length > 20 || + beforeLine.includes(':') || + afterLine.includes('This') || + afterLine.includes('The above') + ); + } + + getLineNumber(node) { + return node.position ? node.position.start.line : null; + } +} + +export default ContentRules; \ No newline at end of file diff --git a/docs-linter/src/rules/formatting.js b/docs-linter/src/rules/formatting.js new file mode 100644 index 00000000..cdaa758c --- /dev/null +++ b/docs-linter/src/rules/formatting.js @@ -0,0 +1,445 @@ +/** + * Formatting Rules - Based on KM Feedback Patterns + * + * These rules implement formatting improvements extracted from KM team feedback, + * focusing on consistency in headings, lists, links, and code blocks. + */ + +import { visit } from 'unist-util-visit'; + +class FormattingRules { + constructor() { + this.ruleSet = [ + this.checkHeadingCapitalization, + this.checkListFormatting, + this.checkLinkFormatting, + this.checkCodeBlockFormatting, + this.checkPunctuationConsistency, + this.checkSpacingConsistency + ]; + } + + /** + * Check all formatting rules against the document + */ + async check(context) { + const issues = []; + + for (const rule of this.ruleSet) { + const ruleIssues = await rule.call(this, context); + issues.push(...ruleIssues); + } + + return issues; + } + + /** + * Check heading capitalization based on KM patterns + */ + checkHeadingCapitalization(context) { + const issues = []; + const { ast } = context; + + visit(ast, 'heading', (node) => { + if (node.children && node.children[0] && node.children[0].type === 'text') { + const text = node.children[0].value; + const line = this.getLineNumber(node); + + // Check for common KM corrections in headings + const corrections = this.getHeadingCorrections(text); + corrections.forEach(correction => { + issues.push({ + id: `heading-${line}-${correction.type}`, + category: 'formatting', + severity: 'warning', + message: correction.message, + line: line, + suggestion: correction.suggestion, + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: text, + to: correction.corrected + } + }); + }); + + // Check for proper title case in main headings (h1, h2) + if (node.depth <= 2) { + const titleCase = this.toTitleCase(text); + if (text !== titleCase && this.shouldUseTitleCase(text)) { + issues.push({ + id: `heading-title-case-${line}`, + category: 'formatting', + severity: 'info', + message: `Consider using title case for heading: "${text}"`, + line: line, + suggestion: `Use: "${titleCase}"`, + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: text, + to: titleCase + } + }); + } + } + } + }); + + return issues; + } + + /** + * Check list formatting consistency + */ + checkListFormatting(context) { + const issues = []; + const { ast } = context; + + visit(ast, 'list', (node) => { + const line = this.getLineNumber(node); + + // Check for consistent bullet style + if (!node.ordered) { + // Unordered list - check for dash vs asterisk consistency + let hasDash = false; + let hasAsterisk = false; + + visit(node, 'listItem', (item) => { + // This would need to check the raw markdown, simplified here + const marker = this.getListMarker(item, context.content); + if (marker === '-') hasDash = true; + if (marker === '*') hasAsterisk = true; + }); + + if (hasDash && hasAsterisk) { + issues.push({ + id: `list-marker-consistency-${line}`, + category: 'formatting', + severity: 'warning', + message: 'Mixed bullet markers in list (use consistent - or * throughout)', + line: line, + suggestion: 'Use consistent bullet markers (prefer dashes "-")', + fixable: true, + safeFix: true, + fix: { + type: 'standardize-list-markers' + } + }); + } + } + + // Check for proper spacing in list items + node.children.forEach((item, index) => { + if (item.children && item.children.length > 0) { + const firstChild = item.children[0]; + if (firstChild.type === 'paragraph' && firstChild.children[0]) { + const text = firstChild.children[0].value; + if (text && !text.startsWith(' ')) { + // This is handled by markdown parser, but check for double spaces + if (text.includes(' ')) { + issues.push({ + id: `list-spacing-${line}-${index}`, + category: 'formatting', + severity: 'info', + message: 'Multiple spaces in list item', + line: this.getLineNumber(item), + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: text, + to: text.replace(/ {2,}/g, ' ') + } + }); + } + } + } + } + }); + }); + + return issues; + } + + /** + * Check link formatting consistency + */ + checkLinkFormatting(context) { + const issues = []; + const { ast } = context; + + visit(ast, 'link', (node) => { + const line = this.getLineNumber(node); + const url = node.url; + const title = node.children[0]?.value || ''; + + // Check for common link formatting issues from KM patterns + if (url && title) { + // Check for "refer to this" vs "see this" patterns + const parentText = this.getParentText(node, context.ast); + if (parentText) { + const linkContext = parentText.toLowerCase(); + + if (linkContext.includes('refer to this') || linkContext.includes('refer to')) { + issues.push({ + id: `link-context-${line}`, + category: 'formatting', + severity: 'info', + message: 'Consider using "see" instead of "refer to" for links', + line: line, + suggestion: 'Use "see" for more natural link text', + fixable: false, + safeFix: false + }); + } + + if (linkContext.includes('for more information around')) { + issues.push({ + id: `link-preposition-${line}`, + category: 'formatting', + severity: 'warning', + message: 'Use "about" instead of "around" in link context', + line: line, + suggestion: 'Use "For more information about" instead of "around"', + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: 'For more information around', + to: 'For more information about' + } + }); + } + } + + // Check for bare URLs that should be formatted as links + if (title === url && url.length > 50) { + issues.push({ + id: `link-title-${line}`, + category: 'formatting', + severity: 'info', + message: 'Consider using descriptive text instead of bare URL', + line: line, + suggestion: 'Use descriptive link text instead of the full URL', + fixable: false, + safeFix: false + }); + } + } + }); + + return issues; + } + + /** + * Check code block formatting + */ + checkCodeBlockFormatting(context) { + const issues = []; + const { ast } = context; + + visit(ast, 'code', (node) => { + const line = this.getLineNumber(node); + const code = node.value; + + // Check for proper language specification + if (!node.lang && code.length > 20) { + issues.push({ + id: `code-lang-${line}`, + category: 'formatting', + severity: 'info', + message: 'Consider specifying language for code block', + line: line, + suggestion: 'Add language identifier (e.g., ```bash, ```javascript)', + fixable: false, + safeFix: false + }); + } + + // Check for common formatting issues in code blocks + if (code.includes('````')) { + issues.push({ + id: `code-fence-${line}`, + category: 'formatting', + severity: 'warning', + message: 'Incorrect code fence formatting (four backticks)', + line: line, + suggestion: 'Use three backticks (```) for code fences', + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: '````', + to: '```' + } + }); + } + }); + + return issues; + } + + /** + * Check punctuation consistency based on KM patterns + */ + checkPunctuationConsistency(context) { + const issues = []; + const { content, corrections } = context; + + if (corrections && corrections.typos) { + const lines = content.split('\n'); + + Object.entries(corrections.typos).forEach(([wrong, correct]) => { + lines.forEach((line, index) => { + if (line.includes(wrong)) { + issues.push({ + id: `punctuation-${index + 1}-${wrong}`, + category: 'formatting', + severity: 'warning', + message: `Punctuation issue: "${wrong}" should be "${correct}"`, + line: index + 1, + suggestion: `Replace "${wrong}" with "${correct}"`, + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: wrong, + to: correct + } + }); + } + }); + }); + } + + return issues; + } + + /** + * Check spacing consistency + */ + checkSpacingConsistency(context) { + const issues = []; + const { content } = context; + const lines = content.split('\n'); + + lines.forEach((line, index) => { + // Check for multiple spaces (except in code blocks) + if (!line.trim().startsWith('```') && !line.trim().startsWith(' ')) { + if (line.match(/ {3,}/)) { + issues.push({ + id: `spacing-multiple-${index + 1}`, + category: 'formatting', + severity: 'info', + message: 'Multiple consecutive spaces found', + line: index + 1, + suggestion: 'Use single spaces between words', + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: line, + to: line.replace(/ {2,}/g, ' ') + } + }); + } + } + + // Check for trailing spaces + if (line.endsWith(' ') && line.trim().length > 0) { + issues.push({ + id: `spacing-trailing-${index + 1}`, + category: 'formatting', + severity: 'info', + message: 'Trailing spaces found', + line: index + 1, + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: line, + to: line.trimEnd() + } + }); + } + }); + + return issues; + } + + // Utility methods + + getLineNumber(node) { + return node.position ? node.position.start.line : null; + } + + getListMarker(item, content) { + // Simplified - would need more complex logic to extract actual marker + const line = this.getLineNumber(item); + if (line && content) { + const lines = content.split('\n'); + const lineContent = lines[line - 1]; + if (lineContent && lineContent.trim().startsWith('- ')) return '-'; + if (lineContent && lineContent.trim().startsWith('* ')) return '*'; + } + return '-'; // default + } + + getParentText(node, ast) { + // Simplified - would need to traverse up to find parent paragraph + return ''; + } + + getHeadingCorrections(text) { + const corrections = []; + + // Based on extracted KM patterns + const patterns = { + 'Support ticket checklist': { + corrected: 'Checklist for Support Tickets', + type: 'title-improvement', + message: 'Heading should be more descriptive', + suggestion: 'Use "Checklist for Support Tickets"' + }, + 'Common causes for deployment errors': { + corrected: 'Common Causes for Deployment Errors', + type: 'capitalization', + message: 'Heading should use title case', + suggestion: 'Capitalize important words in headings' + } + }; + + if (patterns[text]) { + corrections.push(patterns[text]); + } + + return corrections; + } + + toTitleCase(str) { + return str.replace(/\w\S*/g, (txt) => { + // Don't capitalize small words unless they're at the beginning + const smallWords = ['a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'in', 'of', 'on', 'or', 'the', 'to', 'up']; + const word = txt.toLowerCase(); + + if (smallWords.includes(word)) { + return word; + } + + return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); + }); + } + + shouldUseTitleCase(text) { + // Only suggest title case for clearly title-like headings + const titleIndicators = ['overview', 'introduction', 'getting started', 'prerequisites', 'conclusion']; + const lowerText = text.toLowerCase(); + return titleIndicators.some(indicator => lowerText.includes(indicator)); + } +} + +export default FormattingRules; \ No newline at end of file diff --git a/docs-linter/src/rules/structural.js b/docs-linter/src/rules/structural.js new file mode 100644 index 00000000..4ab2e80b --- /dev/null +++ b/docs-linter/src/rules/structural.js @@ -0,0 +1,348 @@ +/** + * Structural Rules - Based on KM Feedback Patterns + * + * These rules ensure consistent document structure, proper heading hierarchy, + * and required sections based on quality examples. + */ + +import { visit } from 'unist-util-visit'; + +class StructuralRules { + constructor() { + this.ruleSet = [ + this.checkRequiredSections, + this.checkHeadingHierarchy, + this.checkTableOfContents, + this.checkSectionOrder, + this.checkDocumentLength + ]; + } + + /** + * Check all structural rules against the document + */ + async check(context) { + const issues = []; + + for (const rule of this.ruleSet) { + const ruleIssues = await rule.call(this, context); + issues.push(...ruleIssues); + } + + return issues; + } + + /** + * Check for required sections based on quality examples + */ + checkRequiredSections(context) { + const issues = []; + const { ast, file } = context; + + const headings = this.extractHeadings(ast); + const headingTexts = headings.map(h => h.text.toLowerCase()); + + // Required sections for README files based on quality examples + if (file.endsWith('README.md')) { + const requiredSections = [ + { name: 'overview', alternatives: ['introduction', 'about'] }, + { name: 'prerequisites', alternatives: ['requirements'] } + ]; + + const recommendedSections = [ + { name: 'getting started', alternatives: ['usage', 'how to use'] }, + { name: 'additional resources', alternatives: ['resources', 'links', 'references'] } + ]; + + requiredSections.forEach(section => { + const hasSection = headingTexts.some(heading => + heading.includes(section.name) || + section.alternatives.some(alt => heading.includes(alt)) + ); + + if (!hasSection) { + issues.push({ + id: `missing-required-section-${section.name}`, + category: 'structural', + severity: 'error', + message: `Missing required section: ${section.name}`, + suggestion: `Add a "${this.capitalizeFirst(section.name)}" section`, + fixable: true, + safeFix: false, + fix: { + type: 'insertAfter', + line: 1, + content: `\n## ${this.capitalizeFirst(section.name)}\n\n[Add ${section.name} content here]\n` + } + }); + } + }); + + recommendedSections.forEach(section => { + const hasSection = headingTexts.some(heading => + heading.includes(section.name) || + section.alternatives.some(alt => heading.includes(alt)) + ); + + if (!hasSection) { + issues.push({ + id: `missing-recommended-section-${section.name}`, + category: 'structural', + severity: 'info', + message: `Consider adding recommended section: ${section.name}`, + suggestion: `Add a "${this.capitalizeFirst(section.name)}" section`, + fixable: false, + safeFix: false + }); + } + }); + + // Check for troubleshooting or known issues section for technical guides + const isTechnicalGuide = file.includes('onpremise') || file.includes('destination') || + headingTexts.some(h => h.includes('configuration') || h.includes('setup')); + + if (isTechnicalGuide) { + const hasTroubleshooting = headingTexts.some(h => + h.includes('troubleshooting') || h.includes('issues') || h.includes('checklist') + ); + + if (!hasTroubleshooting) { + issues.push({ + id: 'missing-troubleshooting', + category: 'structural', + severity: 'warning', + message: 'Technical guides should include troubleshooting or known issues section', + suggestion: 'Add "Known Issues" or "Troubleshooting" section', + fixable: false, + safeFix: false + }); + } + } + } + + return issues; + } + + /** + * Check heading hierarchy (h1 -> h2 -> h3, no skipping levels) + */ + checkHeadingHierarchy(context) { + const issues = []; + const { ast } = context; + + const headings = this.extractHeadings(ast); + let previousDepth = 0; + + headings.forEach((heading, index) => { + const { depth, line } = heading; + + // Check for skipped heading levels + if (depth > previousDepth + 1) { + issues.push({ + id: `heading-hierarchy-skip-${line}`, + category: 'structural', + severity: 'warning', + message: `Heading level skipped: h${depth} after h${previousDepth}`, + line: line, + suggestion: `Use h${previousDepth + 1} instead of h${depth}`, + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: '#'.repeat(depth), + to: '#'.repeat(previousDepth + 1) + } + }); + } + + // Check for multiple h1 headings + if (depth === 1 && index > 0) { + issues.push({ + id: `multiple-h1-${line}`, + category: 'structural', + severity: 'error', + message: 'Multiple h1 headings found - use only one h1 per document', + line: line, + suggestion: 'Change to h2 or merge with existing h1', + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: '#', + to: '##' + } + }); + } + + previousDepth = depth; + }); + + return issues; + } + + /** + * Check for table of contents when needed + */ + checkTableOfContents(context) { + const issues = []; + const { ast, content } = context; + + const headings = this.extractHeadings(ast); + const contentLength = content.length; + + // Long documents should have table of contents + if (contentLength > 10000 && headings.length > 8) { + const hasTOC = content.toLowerCase().includes('table of contents') || + content.toLowerCase().includes('- [') || + content.includes('](#'); + + if (!hasTOC) { + issues.push({ + id: 'missing-toc', + category: 'structural', + severity: 'info', + message: 'Long document should include table of contents', + suggestion: 'Add table of contents after the overview section', + fixable: true, + safeFix: false, + fix: { + type: 'generate-toc' + } + }); + } + } + + return issues; + } + + /** + * Check section order based on quality examples + */ + checkSectionOrder(context) { + const issues = []; + const { ast, file } = context; + + if (!file.endsWith('README.md')) return issues; + + const headings = this.extractHeadings(ast); + const headingTexts = headings.map(h => h.text.toLowerCase()); + + // Expected order based on quality examples + const expectedOrder = [ + 'overview', + 'prerequisites', + 'getting started', + 'configuration', + 'usage', + 'troubleshooting', + 'additional resources', + 'license' + ]; + + // Find sections that exist and check their order + const foundSections = []; + headingTexts.forEach((heading, index) => { + expectedOrder.forEach(expectedSection => { + if (heading.includes(expectedSection)) { + foundSections.push({ + name: expectedSection, + index: index, + line: headings[index].line + }); + } + }); + }); + + // Check if sections are in correct order + for (let i = 1; i < foundSections.length; i++) { + const current = foundSections[i]; + const previous = foundSections[i - 1]; + + const currentExpectedIndex = expectedOrder.indexOf(current.name); + const previousExpectedIndex = expectedOrder.indexOf(previous.name); + + if (currentExpectedIndex < previousExpectedIndex) { + issues.push({ + id: `section-order-${current.line}`, + category: 'structural', + severity: 'info', + message: `Section "${current.name}" should come before "${previous.name}"`, + line: current.line, + suggestion: `Reorder sections to match standard structure`, + fixable: false, + safeFix: false + }); + } + } + + return issues; + } + + /** + * Check document length and structure balance + */ + checkDocumentLength(context) { + const issues = []; + const { content, file } = context; + + const lines = content.split('\n'); + const nonEmptyLines = lines.filter(line => line.trim().length > 0); + + // Very short README files might be incomplete + if (file.endsWith('README.md') && nonEmptyLines.length < 20) { + issues.push({ + id: 'short-readme', + category: 'structural', + severity: 'warning', + message: 'README appears to be very short - consider adding more content', + suggestion: 'Add sections like Overview, Prerequisites, Usage, and Additional Resources', + fixable: false, + safeFix: false + }); + } + + // Very long files without proper structure + if (nonEmptyLines.length > 500) { + const headings = this.extractHeadings(context.ast); + const headingRatio = headings.length / nonEmptyLines.length; + + if (headingRatio < 0.02) { // Less than 2% headings + issues.push({ + id: 'long-unstructured', + category: 'structural', + severity: 'info', + message: 'Long document should have more headings for better structure', + suggestion: 'Break content into sections with descriptive headings', + fixable: false, + safeFix: false + }); + } + } + + return issues; + } + + // Utility methods + + extractHeadings(ast) { + const headings = []; + + visit(ast, 'heading', (node) => { + if (node.children && node.children[0] && node.children[0].type === 'text') { + headings.push({ + depth: node.depth, + text: node.children[0].value, + line: node.position ? node.position.start.line : null + }); + } + }); + + return headings; + } + + capitalizeFirst(str) { + return str.charAt(0).toUpperCase() + str.slice(1); + } +} + +export default StructuralRules; \ No newline at end of file diff --git a/docs-linter/src/rules/technical.js b/docs-linter/src/rules/technical.js new file mode 100644 index 00000000..ab867b15 --- /dev/null +++ b/docs-linter/src/rules/technical.js @@ -0,0 +1,530 @@ +/** + * Technical Rules - Based on KM Feedback Patterns + * + * These rules focus on technical accuracy, URL validation, + * command syntax, and configuration examples. + */ + +import { visit } from 'unist-util-visit'; + +class TechnicalRules { + constructor() { + this.ruleSet = [ + this.checkURLs, + this.checkCommandSyntax, + this.checkConfigurationExamples, + this.checkTechnicalAccuracy, + this.checkVersionReferences + ]; + } + + /** + * Check all technical rules against the document + */ + async check(context) { + const issues = []; + + for (const rule of this.ruleSet) { + const ruleIssues = await rule.call(this, context); + issues.push(...ruleIssues); + } + + return issues; + } + + /** + * Check URLs for validity and format + */ + checkURLs(context) { + const issues = []; + const { ast, corrections } = context; + + visit(ast, 'link', (node) => { + const line = this.getLineNumber(node); + const url = node.url; + + if (url) { + // Validate URL format first + let urlObj; + try { + urlObj = new URL(url); + } catch (error) { + // Invalid URL format - will be caught by other validators + return; + } + + // Check for common URL corrections from KM patterns + if (corrections && corrections.typos) { + Object.entries(corrections.typos).forEach(([wrong, correct]) => { + // Only match whole domain components, not substrings + const hostname = urlObj.hostname.toLowerCase(); + if (hostname.includes(wrong.toLowerCase()) || url.includes(wrong)) { + issues.push({ + id: `url-correction-${line}`, + category: 'technical', + severity: 'error', + message: `URL contains known error: "${wrong}"`, + line: line, + suggestion: `Should be: "${correct}"`, + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: wrong, + to: correct + } + }); + } + }); + } + + // Check for broken or suspicious URLs + if (url.includes('localhost') || url.includes('127.0.0.1')) { + issues.push({ + id: `localhost-url-${line}`, + category: 'technical', + severity: 'warning', + message: 'Localhost URL in documentation', + line: line, + suggestion: 'Replace with example or placeholder URL', + fixable: false, + safeFix: false + }); + } + + // Check for HTTP URLs that should be HTTPS + if (url.startsWith('http://') && !url.includes('localhost')) { + issues.push({ + id: `insecure-url-${line}`, + category: 'technical', + severity: 'info', + message: 'Consider using HTTPS instead of HTTP', + line: line, + suggestion: 'Use HTTPS for better security', + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: 'http://', + to: 'https://' + } + }); + } + + // Check for common SAP URL patterns + // Use proper hostname validation to prevent URL injection attacks + try { + const urlObj = new URL(url); + const hostname = urlObj.hostname.toLowerCase(); + + // Validate that hostname is exactly a SAP domain + // This prevents evil.com/community.sap.com or community.sap.com.evil.com + const isSAPDomain = hostname === 'sap.com' || + hostname.endsWith('.sap.com'); + + // Check for valid SAP subdomain patterns (help.sap.com, community.sap.com, etc.) + const sapSubdomainPattern = /^[a-z0-9]+\.sap\.com$/; + const isSAPSubdomain = sapSubdomainPattern.test(hostname); + + if (isSAPDomain && (isSAPSubdomain || hostname === 'sap.com')) { + if (url.includes(' ') || url.includes('\n')) { + issues.push({ + id: `malformed-sap-url-${line}`, + category: 'technical', + severity: 'error', + message: 'Malformed SAP URL with spaces', + line: line, + suggestion: 'Remove spaces from URL', + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: url, + to: url.replace(/\s+/g, '') + } + }); + } + } + } catch (error) { + // Invalid URL format - will be caught by other validators + } + } + }); + + return issues; + } + + /** + * Check command syntax in code blocks + */ + checkCommandSyntax(context) { + const issues = []; + const { ast } = context; + + visit(ast, 'code', (node) => { + const line = this.getLineNumber(node); + const code = node.value; + const lang = node.lang; + + // Check bash/shell commands + if (lang === 'bash' || lang === 'sh' || lang === 'shell' || !lang) { + const commands = code.split('\n').filter(line => line.trim()); + + commands.forEach((command, index) => { + const trimmedCmd = command.trim(); + + // Check for common command issues + if (trimmedCmd.startsWith('$')) { + issues.push({ + id: `command-prompt-${line}-${index}`, + category: 'technical', + severity: 'info', + message: 'Avoid including $ prompt in command examples', + line: line, + suggestion: 'Remove $ prompt for cleaner copy-paste experience', + fixable: true, + safeFix: true, + fix: { + type: 'replace', + from: command, + to: command.replace(/^\s*\$\s*/, '') + } + }); + } + + // Check for potentially dangerous commands + const dangerousCommands = ['rm -rf', 'dd if=', 'mkfs', 'fdisk']; + if (dangerousCommands.some(dangerous => trimmedCmd.includes(dangerous))) { + issues.push({ + id: `dangerous-command-${line}-${index}`, + category: 'technical', + severity: 'warning', + message: 'Potentially dangerous command in documentation', + line: line, + suggestion: 'Add warning or use safer alternative', + fixable: false, + safeFix: false + }); + } + + // Check for curl commands with proper formatting + if (trimmedCmd.includes('curl')) { + if (!trimmedCmd.includes('-') && trimmedCmd.length > 20) { + issues.push({ + id: `curl-formatting-${line}-${index}`, + category: 'technical', + severity: 'info', + message: 'Complex curl command might benefit from formatting', + line: line, + suggestion: 'Consider using line breaks and flags for readability', + fixable: false, + safeFix: false + }); + } + } + + // Check for SAP-specific commands + if (trimmedCmd.includes('cf ') && !trimmedCmd.includes('--help')) { + // Check for missing target specification + const cfCommands = ['cf push', 'cf create-service', 'cf bind-service']; + if (cfCommands.some(cfCmd => trimmedCmd.includes(cfCmd))) { + if (!code.includes('cf target') && !trimmedCmd.includes('-t ')) { + issues.push({ + id: `cf-target-missing-${line}-${index}`, + category: 'technical', + severity: 'info', + message: 'CF command without target context', + line: line, + suggestion: 'Consider showing cf target command or specifying org/space', + fixable: false, + safeFix: false + }); + } + } + } + }); + } + + // Check JSON/YAML configuration blocks + if (lang === 'json' || lang === 'yaml' || lang === 'yml') { + try { + if (lang === 'json') { + JSON.parse(code); + } + // YAML parsing would require a library, simplified here + } catch (error) { + issues.push({ + id: `invalid-json-${line}`, + category: 'technical', + severity: 'error', + message: `Invalid ${lang} syntax`, + line: line, + suggestion: 'Fix syntax errors', + fixable: false, + safeFix: false + }); + } + + // Check for placeholder values in configs + const placeholders = ['', '[YOUR_VALUE]', 'TODO', 'CHANGEME']; + placeholders.forEach(placeholder => { + if (code.includes(placeholder)) { + issues.push({ + id: `config-placeholder-${line}`, + category: 'technical', + severity: 'warning', + message: `Configuration contains placeholder: ${placeholder}`, + line: line, + suggestion: 'Replace with example values or clear instructions', + fixable: false, + safeFix: false + }); + } + }); + } + }); + + return issues; + } + + /** + * Check configuration examples for completeness + */ + checkConfigurationExamples(context) { + const issues = []; + const { content } = context; + + // Check for SAP BTP destination configurations + if (content.includes('destination') || content.includes('BTP')) { + const configSections = this.extractConfigurationBlocks(content); + + configSections.forEach(config => { + // Check for required destination properties + const requiredProps = ['Name', 'Type', 'URL']; + const missingProps = requiredProps.filter(prop => !config.content.includes(prop)); + + if (missingProps.length > 0 && config.content.includes('Type')) { + issues.push({ + id: `incomplete-destination-config-${config.line}`, + category: 'technical', + severity: 'warning', + message: `Destination configuration missing properties: ${missingProps.join(', ')}`, + line: config.line, + suggestion: 'Add missing required properties', + fixable: false, + safeFix: false + }); + } + + // Check for authentication configuration + if (config.content.includes('Authentication') || config.content.includes('OAuth')) { + const authProps = ['clientId', 'clientSecret', 'tokenServiceURL']; + const hasAuthProps = authProps.some(prop => config.content.includes(prop)); + + if (config.content.includes('OAuth') && !hasAuthProps) { + issues.push({ + id: `incomplete-oauth-config-${config.line}`, + category: 'technical', + severity: 'warning', + message: 'OAuth configuration missing required properties', + line: config.line, + suggestion: 'Add clientId, clientSecret, and tokenServiceURL', + fixable: false, + safeFix: false + }); + } + } + }); + } + + return issues; + } + + /** + * Check for technical accuracy based on KM patterns + */ + checkTechnicalAccuracy(context) { + const issues = []; + const { content, patterns } = context; + + // Check for technical corrections from KM patterns + if (patterns && patterns.technical) { + patterns.technical.forEach(pattern => { + if (pattern.before && pattern.after && pattern.before !== pattern.after) { + const lines = content.split('\n'); + + lines.forEach((line, index) => { + if (line.includes(pattern.before)) { + issues.push({ + id: `technical-accuracy-${index + 1}`, + category: 'technical', + severity: 'warning', + message: 'Technical information could be more accurate', + line: index + 1, + suggestion: `Consider: "${pattern.after}"`, + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: pattern.before, + to: pattern.after + } + }); + } + }); + } + }); + } + + // Check for common technical inaccuracies + const lines = content.split('\n'); + lines.forEach((line, index) => { + const lowerLine = line.toLowerCase(); + + // Check for outdated protocol references + if (lowerLine.includes('ssl') && !lowerLine.includes('tls')) { + if (lowerLine.includes('certificate') || lowerLine.includes('connection')) { + issues.push({ + id: `ssl-outdated-${index + 1}`, + category: 'technical', + severity: 'info', + message: 'Consider mentioning TLS in addition to SSL', + line: index + 1, + suggestion: 'Use "SSL/TLS" or "TLS" for modern security', + fixable: false, + safeFix: false + }); + } + } + + // Check for version-specific information without version numbers + const versionSensitive = [ + 'current version', 'latest version', 'new feature', + 'recently added', 'now supports' + ]; + + versionSensitive.forEach(phrase => { + if (lowerLine.includes(phrase)) { + issues.push({ + id: `version-vague-${index + 1}`, + category: 'technical', + severity: 'info', + message: `Vague version reference: "${phrase}"`, + line: index + 1, + suggestion: 'Specify exact version numbers when possible', + fixable: false, + safeFix: false + }); + } + }); + }); + + return issues; + } + + /** + * Check version references and compatibility information + */ + checkVersionReferences(context) { + const issues = []; + const { content } = context; + + const lines = content.split('\n'); + + lines.forEach((line, index) => { + // Check for year references that might be outdated + if (line.includes('2025') || line.includes('2024')) { + const currentYear = new Date().getFullYear(); + if (currentYear > 2026) { // Only flag if significantly outdated + issues.push({ + id: `outdated-year-${index + 1}`, + category: 'technical', + severity: 'info', + message: 'Year reference might be outdated', + line: index + 1, + suggestion: `Consider updating to ${currentYear}`, + fixable: true, + safeFix: false, + fix: { + type: 'replace', + from: line.match(/202[4-5]/)[0], + to: currentYear.toString() + } + }); + } + } + + // Check for Node.js version references + const nodeVersionMatch = line.match(/node\s+(\d+)/i); + if (nodeVersionMatch) { + const version = parseInt(nodeVersionMatch[1]); + if (version < 16) { // Node 16+ is recommended + issues.push({ + id: `node-version-${index + 1}`, + category: 'technical', + severity: 'warning', + message: `Node.js version ${version} is outdated`, + line: index + 1, + suggestion: 'Recommend Node.js 18+ for better support', + fixable: false, + safeFix: false + }); + } + } + + // Check for SAP UI5 version references + if (line.includes('UI5') && line.match(/\d+\.\d+/)) { + const versionMatch = line.match(/(\d+\.\d+)/); + if (versionMatch) { + const version = parseFloat(versionMatch[1]); + if (version < 1.90) { + issues.push({ + id: `ui5-version-${index + 1}`, + category: 'technical', + severity: 'info', + message: `UI5 version ${version} might be outdated`, + line: index + 1, + suggestion: 'Consider referencing newer UI5 versions', + fixable: false, + safeFix: false + }); + } + } + } + }); + + return issues; + } + + // Utility methods + + getLineNumber(node) { + return node.position ? node.position.start.line : null; + } + + extractConfigurationBlocks(content) { + const blocks = []; + const lines = content.split('\n'); + let currentBlock = null; + + lines.forEach((line, index) => { + // Simple detection of configuration blocks + if (line.trim().startsWith('```') && (line.includes('json') || line.includes('yaml'))) { + currentBlock = { + line: index + 1, + content: '', + type: line.includes('json') ? 'json' : 'yaml' + }; + } else if (currentBlock && line.trim() === '```') { + blocks.push(currentBlock); + currentBlock = null; + } else if (currentBlock) { + currentBlock.content += line + '\n'; + } + }); + + return blocks; + } +} + +export default TechnicalRules; \ No newline at end of file diff --git a/docs-linter/src/template-generator.js b/docs-linter/src/template-generator.js new file mode 100644 index 00000000..6f4e4235 --- /dev/null +++ b/docs-linter/src/template-generator.js @@ -0,0 +1,503 @@ +/** + * Template Generator - Based on Quality Examples + * + * Generates documentation templates based on high-quality examples + * identified from the KM feedback analysis. + */ + +import { readFileSync, existsSync } from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +// ESM equivalent of __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +class TemplateGenerator { + constructor() { + this.loadTemplateData(); + } + + /** + * Load template data and quality examples + */ + loadTemplateData() { + try { + const trainingDataPath = path.resolve(__dirname, '../../training-data'); + + if (existsSync(path.join(trainingDataPath, 'quality-examples.json'))) { + const examples = JSON.parse(readFileSync( + path.join(trainingDataPath, 'quality-examples.json'), + 'utf8' + )); + this.qualityExamples = examples; + } + } catch (error) { + console.warn('Warning: Could not load template data:', error.message); + this.qualityExamples = []; + } + } + + /** + * Generate documentation from template + */ + async generate(templateType, options = {}) { + switch (templateType) { + case 'sample-app': + return this.generateSampleAppTemplate(options); + case 'guide': + return this.generateGuideTemplate(options); + case 'api': + return this.generateAPITemplate(options); + case 'troubleshooting': + return this.generateTroubleshootingTemplate(options); + default: + return this.generateDefaultTemplate(options); + } + } + + /** + * Generate sample application README template + */ + generateSampleAppTemplate(options) { + const template = `# Sample Application Name + +## Overview +Brief description of what this sample application demonstrates. Explain the business scenario, technologies used, and learning objectives. + +## Prerequisites +- SAP BTP account with Cloud Foundry runtime +- [List specific prerequisites based on the application] +- Node.js 18 or higher +- Access to SAP Business Application Studio or VS Code + +## Getting Started + +### 1. Clone and Setup +\`\`\`bash +git clone [repository-url] +cd [project-directory] +npm install +\`\`\` + +### 2. Configuration +[Provide step-by-step configuration instructions] + +### 3. Deploy and Run +\`\`\`bash +npm run build +npm start +\`\`\` + +## Features Demonstrated +- [Feature 1 with brief explanation] +- [Feature 2 with brief explanation] +- [Feature 3 with brief explanation] + +## Project Structure +\`\`\` +project-root/ +├── app/ # Application code +├── srv/ # Service layer +├── db/ # Database definitions +├── package.json # Dependencies and scripts +└── README.md # This file +\`\`\` + +## Configuration Details +[Detailed configuration information with examples] + +## Troubleshooting +### Common Issues +1. **Issue 1**: Description and solution +2. **Issue 2**: Description and solution + +### Support +For support tickets, include: +- Error messages and logs +- Steps to reproduce +- Environment details + +## Additional Resources +- [SAP BTP Documentation](https://help.sap.com/docs/btp) +- [Related tutorials and guides] + +## License +This project is licensed under the Apache 2.0 License - see the LICENSE file for details. + +--- +*Generated using KM Documentation Linter v1.0*`; + + return this.processTemplate(template, options); + } + + /** + * Generate technical guide template + */ + generateGuideTemplate(options) { + const template = `# [Guide Title] + +## Overview +[Brief overview of what this guide covers and its purpose] + +## Table of Contents +- [Overview](#overview) +- [Prerequisites](#prerequisites) +- [Step-by-Step Instructions](#step-by-step-instructions) +- [Configuration Details](#configuration-details) +- [Validation and Testing](#validation-and-testing) +- [Troubleshooting](#troubleshooting) +- [Additional Resources](#additional-resources) + +## Prerequisites +- [Prerequisite 1 with link if applicable] +- [Prerequisite 2 with version requirements] +- [Access requirements and permissions] + +## Step-by-Step Instructions + +### Step 1: [First Major Step] +[Detailed instructions with screenshots if helpful] + +\`\`\`bash +# Example commands +command-example +\`\`\` + +### Step 2: [Second Major Step] +[Detailed instructions] + +### Step 3: [Third Major Step] +[Detailed instructions] + +## Configuration Details +[Detailed configuration with examples] + +\`\`\`json +{ + "example": "configuration", + "property": "value" +} +\`\`\` + +## Validation and Testing +1. **Validate Setup**: [How to verify the configuration] +2. **Test Functionality**: [How to test that everything works] +3. **Expected Results**: [What success looks like] + +## Troubleshooting + +### Quick Checks +- [Quick check 1] +- [Quick check 2] +- [Quick check 3] + +### Common Issues +1. **[Issue Title]**: Description and resolution +2. **[Issue Title]**: Description and resolution + +### Checklist for Support Tickets +If you need to raise a support ticket, include: +- [Required information 1] +- [Required information 2] +- [Log files and error messages] + +## Additional Resources +- [Related documentation] +- [Community resources] +- [Training materials] + +## Known Issues +- [Known limitation 1] +- [Known limitation 2] + +--- +*Generated using KM Documentation Linter v1.0*`; + + return this.processTemplate(template, options); + } + + /** + * Generate API documentation template + */ + generateAPITemplate(options) { + const template = `# API Documentation + +## Overview +[Brief description of the API, its purpose, and main capabilities] + +## Authentication +[Authentication method and requirements] + +\`\`\`http +Authorization: Bearer +\`\`\` + +## Base URL +\`\`\` +https://api.example.com/v1 +\`\`\` + +## Endpoints + +### GET /resource +[Description of what this endpoint does] + +**Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| param1 | string | Yes | Description | +| param2 | integer | No | Description | + +**Example Request:** +\`\`\`http +GET /resource?param1=value1¶m2=123 +\`\`\` + +**Example Response:** +\`\`\`json +{ + "status": "success", + "data": { + "id": 1, + "name": "Example" + } +} +\`\`\` + +### POST /resource +[Description of what this endpoint does] + +**Request Body:** +\`\`\`json +{ + "name": "string", + "description": "string" +} +\`\`\` + +**Example Response:** +\`\`\`json +{ + "status": "success", + "message": "Resource created successfully" +} +\`\`\` + +## Error Handling +[Description of error response format] + +**Error Response:** +\`\`\`json +{ + "status": "error", + "message": "Error description", + "code": "ERROR_CODE" +} +\`\`\` + +## Rate Limiting +[Rate limiting information] + +## Examples +[Practical examples of common use cases] + +## SDKs and Libraries +[Available SDKs and code libraries] + +--- +*Generated using KM Documentation Linter v1.0*`; + + return this.processTemplate(template, options); + } + + /** + * Generate troubleshooting guide template + */ + generateTroubleshootingTemplate(options) { + const template = `# Troubleshooting Guide + +## Overview +This guide helps you resolve common issues with [system/application name]. + +## Quick Diagnostic Steps +1. **Check System Status**: [How to verify system is running] +2. **Verify Configuration**: [Key configuration to check] +3. **Review Logs**: [Where to find relevant logs] + +## Common Issues + +### Issue 1: [Connection Problems] +**Symptoms:** +- [Symptom 1] +- [Symptom 2] + +**Possible Causes:** +- [Cause 1] +- [Cause 2] + +**Resolution:** +1. [Step 1] +2. [Step 2] +3. [Step 3] + +**Verification:** +[How to confirm the issue is resolved] + +### Issue 2: [Authentication Failures] +**Symptoms:** +- [Symptom 1] +- [Symptom 2] + +**Possible Causes:** +- [Cause 1] +- [Cause 2] + +**Resolution:** +1. [Step 1] +2. [Step 2] + +## Log Analysis +### Finding Logs +[Where to locate log files] + +### Key Log Entries +\`\`\` +[Example log entries to look for] +\`\`\` + +### Log Level Configuration +[How to adjust logging levels for debugging] + +## Support Information +### Before Contacting Support +Collect the following information: +- [Required information 1] +- [Required information 2] +- [System configuration details] + +### Support Channels +- [Support method 1] +- [Support method 2] + +## Additional Resources +- [Documentation links] +- [Community forums] +- [Knowledge base articles] + +--- +*Generated using KM Documentation Linter v1.0*`; + + return this.processTemplate(template, options); + } + + /** + * Generate default template based on quality examples + */ + generateDefaultTemplate(options) { + const template = `# Documentation Title + +## Overview +[Provide a clear overview of what this document covers] + +## Prerequisites +- [List prerequisites here] + +## [Main Content Section] +[Your main content goes here] + +## Additional Resources +- [Link to related documentation] + +## License +[License information if applicable] + +--- +*Generated using KM Documentation Linter v1.0*`; + + return this.processTemplate(template, options); + } + + /** + * Process template with options and replacements + */ + processTemplate(template, options) { + let processedTemplate = template; + + // Replace common placeholders + const replacements = { + '[repository-url]': options.repoUrl || '[REPOSITORY_URL]', + '[project-directory]': options.projectDir || '[PROJECT_DIRECTORY]', + '[Guide Title]': options.title || '[GUIDE_TITLE]', + '[system/application name]': options.systemName || '[SYSTEM_NAME]' + }; + + Object.entries(replacements).forEach(([placeholder, value]) => { + processedTemplate = processedTemplate.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), value); + }); + + // Add current date if not provided + const currentDate = new Date().toISOString().split('T')[0]; + if (options.includeDate !== false) { + processedTemplate = `\n${processedTemplate}`; + } + + return processedTemplate; + } + + /** + * Get available templates + */ + getAvailableTemplates() { + return [ + { + name: 'sample-app', + description: 'Template for sample application documentation', + useCase: 'Use for demo applications and code samples' + }, + { + name: 'guide', + description: 'Template for technical guides and how-to documentation', + useCase: 'Use for step-by-step instructions and configuration guides' + }, + { + name: 'api', + description: 'Template for API documentation', + useCase: 'Use for REST API documentation and reference guides' + }, + { + name: 'troubleshooting', + description: 'Template for troubleshooting and support guides', + useCase: 'Use for problem resolution and diagnostic information' + } + ]; + } + + /** + * Analyze existing file to suggest template type + */ + suggestTemplateType(filePath) { + if (!existsSync(filePath)) { + return 'sample-app'; // Default + } + + const content = readFileSync(filePath, 'utf8').toLowerCase(); + + if (content.includes('api') || content.includes('endpoint') || content.includes('rest')) { + return 'api'; + } + + if (content.includes('troubleshooting') || content.includes('issues') || content.includes('error')) { + return 'troubleshooting'; + } + + if (content.includes('guide') || content.includes('step') || content.includes('configuration')) { + return 'guide'; + } + + return 'sample-app'; + } +} + +export default TemplateGenerator; \ No newline at end of file diff --git a/docs-linter/src/utils/data-loader.js b/docs-linter/src/utils/data-loader.js new file mode 100644 index 00000000..c3fbf4c8 --- /dev/null +++ b/docs-linter/src/utils/data-loader.js @@ -0,0 +1,80 @@ +/** + * Data Loader Utilities + * + * Utilities for loading training data and configuration files + */ + +import { readFileSync, existsSync } from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +// ESM equivalent of __dirname +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +/** + * Load training data from extracted patterns + */ +function loadTrainingData() { + try { + const trainingDataPath = path.resolve(__dirname, '../../../training-data'); + + const data = {}; + + // Load patterns + const patternsFile = path.join(trainingDataPath, 'km-feedback-patterns.json'); + if (existsSync(patternsFile)) { + data.patterns = JSON.parse(readFileSync(patternsFile, 'utf8')); + } + + // Load corrections + const correctionsFile = path.join(trainingDataPath, 'correction-dictionary.json'); + if (existsSync(correctionsFile)) { + data.corrections = JSON.parse(readFileSync(correctionsFile, 'utf8')); + } + + // Load quality examples + const examplesFile = path.join(trainingDataPath, 'quality-examples.json'); + if (existsSync(examplesFile)) { + data.qualityExamples = JSON.parse(readFileSync(examplesFile, 'utf8')); + } + + return data; + } catch (error) { + console.warn('Warning: Could not load training data:', error.message); + return {}; + } +} + +/** + * Load rule configuration + */ +function loadRules() { + try { + const rulesPath = path.resolve(__dirname, '../../rules'); + const rules = {}; + + // Load structural rules + const structuralFile = path.join(rulesPath, 'structural.json'); + if (existsSync(structuralFile)) { + rules.structural = JSON.parse(readFileSync(structuralFile, 'utf8')); + } + + // Load formatting rules + const formattingFile = path.join(rulesPath, 'formatting.json'); + if (existsSync(formattingFile)) { + rules.formatting = JSON.parse(readFileSync(formattingFile, 'utf8')); + } + + return rules; + } catch (error) { + console.warn('Warning: Could not load rules:', error.message); + return {}; + } +} + +export { + loadTrainingData, + loadRules +}; \ No newline at end of file diff --git a/docs-linter/test/smoke.test.js b/docs-linter/test/smoke.test.js new file mode 100644 index 00000000..30a69e26 --- /dev/null +++ b/docs-linter/test/smoke.test.js @@ -0,0 +1,117 @@ +/** + * Smoke tests - verify all modules import and basic functionality works + */ + +import { test } from 'node:test'; +import assert from 'node:assert/strict'; + +import DocsLinter from '../src/linter.js'; +import StructuralRules from '../src/rules/structural.js'; +import FormattingRules from '../src/rules/formatting.js'; +import ContentRules from '../src/rules/content.js'; +import TechnicalRules from '../src/rules/technical.js'; + +const MINIMAL_MD = `# Overview\n\nThis is a test document.\n\n## Prerequisites\n\nNone.\n`; + +// --- Module imports --- + +test('DocsLinter instantiates without error', () => { + const linter = new DocsLinter(); + assert.ok(linter); + assert.ok(linter.processor); +}); + +test('StructuralRules instantiates without error', () => { + assert.ok(new StructuralRules()); +}); + +test('FormattingRules instantiates without error', () => { + assert.ok(new FormattingRules()); +}); + +test('ContentRules instantiates without error', () => { + assert.ok(new ContentRules()); +}); + +test('TechnicalRules instantiates without error', () => { + assert.ok(new TechnicalRules()); +}); + +// --- Core linter --- + +test('DocsLinter.processor parses markdown to AST', () => { + const linter = new DocsLinter(); + const ast = linter.processor.parse(MINIMAL_MD); + assert.equal(ast.type, 'root'); + assert.ok(Array.isArray(ast.children)); + assert.ok(ast.children.length > 0); +}); + +// --- Rule checks return arrays --- + +function makeContext(content, file = 'README.md') { + const linter = new DocsLinter(); + const ast = linter.processor.parse(content); + return { content, file, ast, patterns: null, corrections: null, options: {} }; +} + +test('StructuralRules.check returns an array', async () => { + const issues = await new StructuralRules().check(makeContext(MINIMAL_MD)); + assert.ok(Array.isArray(issues)); +}); + +test('FormattingRules.check returns an array', async () => { + const issues = await new FormattingRules().check(makeContext(MINIMAL_MD)); + assert.ok(Array.isArray(issues)); +}); + +test('ContentRules.check returns an array', async () => { + const issues = await new ContentRules().check(makeContext(MINIMAL_MD)); + assert.ok(Array.isArray(issues)); +}); + +test('TechnicalRules.check returns an array', async () => { + const issues = await new TechnicalRules().check(makeContext(MINIMAL_MD)); + assert.ok(Array.isArray(issues)); +}); + +// --- Issue shape --- + +test('issues have required fields when returned', async () => { + const issues = await new StructuralRules().check(makeContext('# Title\n\nContent only.\n')); + issues.forEach(issue => { + assert.ok(typeof issue.id === 'string', 'issue.id must be a string'); + assert.ok(typeof issue.category === 'string', 'issue.category must be a string'); + assert.ok(typeof issue.severity === 'string', 'issue.severity must be a string'); + assert.ok(typeof issue.message === 'string', 'issue.message must be a string'); + }); +}); + +// --- Quality score --- + +test('calculateQualityScore returns a number between 0 and 100', () => { + const score = new DocsLinter().calculateQualityScore([], 'README.md'); + assert.ok(score >= 0 && score <= 100); +}); + +test('calculateQualityScore decrements for errors, warnings, and info', () => { + const issues = [{ severity: 'error' }, { severity: 'warning' }, { severity: 'info' }]; + const score = new DocsLinter().calculateQualityScore(issues, 'README.md'); + assert.equal(score, 100 - 10 - 5 - 2); +}); + +// --- generateSummary --- + +test('generateSummary returns correct counts', () => { + const issues = [ + { severity: 'error', category: 'structural', fixable: true }, + { severity: 'warning', category: 'formatting', fixable: false }, + { severity: 'info', category: 'content', fixable: true } + ]; + const summary = new DocsLinter().generateSummary(issues); + assert.equal(summary.total, 3); + assert.equal(summary.errors, 1); + assert.equal(summary.warnings, 1); + assert.equal(summary.info, 1); + assert.equal(summary.fixable, 2); +}); diff --git a/docs/golden-docs.md b/docs/golden-docs.md new file mode 100644 index 00000000..244d99a9 --- /dev/null +++ b/docs/golden-docs.md @@ -0,0 +1,281 @@ +# Golden Documentation Examples + +## Overview +This guide identifies exemplary documentation files in the repository that consistently receive minimal KM feedback and demonstrate best practices. Use these as references when creating or improving documentation. + +## Quality Scoring Methodology + +Documentation quality is scored based on: +- **Structural Quality** (30%): Heading hierarchy, section organization, completeness +- **Content Quality** (40%): Clarity, accuracy, usefulness, no placeholders +- **Technical Quality** (30%): Valid code, working links, current references + +**Score Calculation:** +- Base score: 100 points +- Deductions: Errors (-10), Warnings (-5), Info issues (-2) +- Bonuses: Quality example status (+10), comprehensive coverage (+5) + +## Gold Standard Examples + +### 1. Destinations Guide - `./misc/destinations/README.md` +**Gold Standard Reference** | **300+ lines** | **Comprehensive Technical Guide** + +**Why It's Excellent:** +- **Complete coverage**: From basic setup to advanced troubleshooting +- **Professional structure**: Clear flow from overview to deep technical details +- **Rich troubleshooting**: Extensive problem-solving section with real scenarios +- **Support readiness**: Professional support ticket checklist with required artifacts +- **Technical depth**: Includes flow diagrams, multiple endpoint examples, validation steps + +**Key Strengths:** +- Mermaid sequence diagram illustrating the flow +- Multiple service endpoint examples with explanations +- Detailed curl commands with expected outputs +- Comprehensive troubleshooting section addressing real user issues +- Professional support ticket requirements + +**Use This For:** +- Technical configuration guides +- API integration documentation +- Troubleshooting-heavy guides + +**Representative Sections:** +```markdown +## Flow Diagram +[Includes professional Mermaid diagram] + +## Sample Microsoft OData XML Service Endpoints +[Multiple working examples with context] + +## Troubleshooting +### Quick Checks +[Systematic diagnostic approach] +``` + +### 2. Headless Fiori UI - `./misc/headless/fioriui/README.md` +**Quality Score: 55.9** | **36,941 bytes** | **Stable & Comprehensive** + +**Why It's Excellent:** +- **Comprehensive scope**: Covers complete headless UI implementation +- **Stable content**: Only 1 recent commit indicating maturity +- **Technical depth**: Detailed implementation with code examples +- **Clear structure**: Logical progression from setup to advanced topics + +**Key Strengths:** +- Extensive technical implementation details +- Well-organized code examples +- Clear prerequisites and setup instructions +- Comprehensive coverage without being overwhelming + +**Use This For:** +- Complex technical implementations +- Headless/UI5 development guides +- Multi-step technical processes + +### 3. Fiori Generator Extension - `./sample-fiori-gen-ext/README.md` +**Quality Score: 55.4** | **13,389 bytes** | **Sample Application Documentation** + +**Why It's Excellent:** +- **Sample app focus**: Perfect template for demo/sample documentation +- **No recent changes**: 16 total commits, 0 recent = stable, quality content +- **Balanced detail**: Comprehensive without being overwhelming +- **Good structure**: Clear flow for sample application documentation + +**Key Strengths:** +- Clear business context and use case explanation +- Step-by-step setup and configuration +- Appropriate level of detail for sample code +- Good balance of overview and technical detail + +**Use This For:** +- Sample application README files +- Demo project documentation +- Quick-start guides + +### 4. CI/CD Guide - `./misc/cicd/README.md` +**Quality Score: 32.2** | **6,227 bytes** | **Focused Technical Guide** + +**Why It's Good:** +- **Focused scope**: Clear, specific topic coverage +- **Stable content**: No recent changes indicate quality baseline +- **Appropriate length**: Right-sized for the topic +- **Technical accuracy**: Solid technical implementation guide + +**Use This For:** +- Focused technical topics +- CI/CD and automation guides +- Medium-length technical documentation + +### 5. CAP Fiori Hybrid - `./cap/cap-fiori-hybrid/README.md` +**Quality Score: 27.1** | **5,145 bytes** | **Hybrid Development Guide** + +**Why It's Useful:** +- **Hybrid architecture**: Good example of complex topic handling +- **Recent updates**: Shows active maintenance while maintaining quality +- **Clear scope**: Focused on specific development pattern + +**Use This For:** +- Hybrid development scenarios +- CAP-related documentation +- Architecture-specific guides + +## Quality Patterns Analysis + +### Structural Excellence Patterns + +**Consistent Section Ordering:** +1. Overview/Title +2. Prerequisites +3. Step-by-step instructions +4. Configuration details +5. Troubleshooting/Known issues +6. Additional resources + +**Heading Hierarchy Best Practices:** +- Single H1 title +- Logical H2 main sections +- H3 subsections as needed +- No level skipping + +**Table of Contents Usage:** +- Present in longer documents (>10k characters) +- Links to all major sections +- Clean, navigable structure + +### Content Excellence Patterns + +**Clear Business Context:** +- Every guide starts with "why" not just "how" +- Business scenarios clearly explained +- Value proposition articulated upfront + +**Complete Prerequisites:** +- Specific version requirements +- Required access and permissions +- Knowledge assumptions clearly stated +- Links to prerequisite setup guides + +**Step-by-Step Clarity:** +- Numbered steps with clear outcomes +- Command examples that work as-is +- Expected results clearly stated +- Validation steps provided + +### Technical Excellence Patterns + +**Code Quality:** +- All code blocks have language specification +- Commands are copy-pasteable (no $ prompts) +- JSON/YAML examples are valid +- Realistic example values used + +**Link Quality:** +- All external links working and current +- Internal links properly anchored +- HTTPS used when available +- Descriptive link text (no bare URLs) + +**Support Information:** +- Clear escalation path for issues +- Required information for support tickets +- Common issues and solutions provided +- Contact information current + +## Anti-Patterns to Avoid + +Based on lower-scoring documentation: + +### Structural Problems +- ❌ Multiple H1 headings +- ❌ Skipped heading levels (H1 → H3) +- ❌ Missing required sections (Overview, Prerequisites) +- ❌ Illogical section ordering + +### Content Problems +- ❌ Placeholder text ([TODO], [TBD]) +- ❌ Vague instructions ("configure appropriately") +- ❌ Missing business context +- ❌ Incomplete prerequisites + +### Technical Problems +- ❌ Broken links +- ❌ Invalid code examples +- ❌ Missing language in code blocks +- ❌ Outdated version references + +## Using These Examples + +### For New Documentation +1. **Choose a similar example**: Match your content type to the most similar gold standard +2. **Follow the structure**: Use the same section ordering and hierarchy +3. **Match the tone**: Professional, clear, action-oriented +4. **Include troubleshooting**: All technical guides need problem-solving sections + +### For Improving Existing Documentation +1. **Compare structure**: Does your doc follow the same logical flow? +2. **Check completeness**: Are you missing sections present in quality examples? +3. **Review technical details**: Are your examples as complete and accurate? +4. **Validate support information**: Do you provide the same level of troubleshooting help? + +### Quality Benchmarking +1. **Run docs-linter**: `docs-linter validate README.md --km-standards` +2. **Compare scores**: Target the scores of these examples or higher +3. **Check against patterns**: Review your content against the excellence patterns above +4. **Test usability**: Can someone new follow your documentation successfully? + +## Continuous Quality Improvement + +### Regular Reviews +- **Monthly**: Review quality scores and identify declining documentation +- **Quarterly**: Update this list with new quality examples +- **Annually**: Refresh patterns based on evolved standards + +### Quality Metrics Tracking +- Monitor average quality scores across repository +- Track which documentation receives most KM feedback +- Identify patterns in user questions and support requests + +### Community Contributions +- Nominate documentation for quality example status +- Share improvements and patterns discovered +- Contribute to KM style guide evolution + +## Quality Example Verification + +To verify a document meets quality example standards: + +```bash +# Check quality score +docs-linter validate README.md --km-standards + +# Comprehensive analysis +docs-linter check README.md --comprehensive + +# Compare to current examples +git log --oneline README.md | head -5 +``` + +**Quality Example Criteria:** +- Quality score ≥ 90/100 +- Minimal recent KM feedback (< 3 commits in 6 months) +- Substantial content (> 5,000 bytes) +- Complete structure with all recommended sections +- Working code examples and links + +## Template Generation + +Generate documentation based on these examples: + +```bash +# Generate using quality patterns +docs-linter template --type=guide --output=NEW_README.md + +# Use specific example as template +docs-linter template --type=sample-app --output=SAMPLE_README.md +``` + +The template generator incorporates patterns from these quality examples to ensure new documentation starts with proven structures and content approaches. + +--- +*Quality examples identified through analysis of commit history, KM feedback patterns, and automated quality scoring* +*Last updated: January 2026 | Based on 5 top-scoring documentation files* \ No newline at end of file diff --git a/docs/km-pr-checklist.md b/docs/km-pr-checklist.md new file mode 100644 index 00000000..d4b7ccd7 --- /dev/null +++ b/docs/km-pr-checklist.md @@ -0,0 +1,223 @@ +# KM PR Checklist - Pre-Review Validation + +## Overview +This checklist ensures documentation changes meet KM standards before review, reducing review cycles and maintaining quality consistency. Follow this checklist for all documentation PRs. + +## Automated Validation + +### Run docs-linter Tool +Before creating your PR, run the automated checks: + +```bash +# Basic check with auto-fix for safe issues +docs-linter check README.md --auto-fix-safe + +# Comprehensive analysis +docs-linter check README.md --comprehensive + +# Validate against KM standards +docs-linter validate README.md --km-standards +``` + +**Expected Results:** +- ✅ No errors (red ❌ items) +- ✅ Minimal warnings (yellow ⚠️ items) +- ✅ Quality score ≥ 75/100 for substantial documentation + +## Manual Review Checklist + +### 🏗️ Structure and Organization + +- [ ] **Single H1 heading** - Only one main heading per document +- [ ] **Logical heading hierarchy** - No skipped levels (H1→H2→H3) +- [ ] **Required sections present** (for README files): + - [ ] Overview/Introduction + - [ ] Prerequisites + - [ ] Main content (Getting Started/Configuration) + - [ ] Additional Resources +- [ ] **Recommended sections** (for technical guides): + - [ ] Troubleshooting or Known Issues + - [ ] Support/Contact information +- [ ] **Table of contents** - Present for documents >10k characters with >8 headings +- [ ] **Logical section order** - Follows standard: Overview → Prerequisites → Instructions → Troubleshooting → Resources + +### ✍️ Content Quality + +- [ ] **No placeholder text** - No [TODO], [TBD], [Add content here], etc. +- [ ] **Complete sentences and lists** - No unfinished content after "such as:", "including:" +- [ ] **Clear, specific language** - Avoid vague terms like "appropriate," "some users," "might work" +- [ ] **Active voice preferred** - "Configure the destination" vs "The destination should be configured" +- [ ] **Consistent terminology**: + - [ ] "on-premise" (not "onpremise" or "on premise") + - [ ] "SAP BTP" (consistent throughout) + - [ ] "Cloud Connector" (properly capitalised) + - [ ] "see" vs "refer to" (prefer "see") + - [ ] "about" vs "around" (prefer "about") + - [ ] "using" vs "via" (prefer "using", e.g. "using Cloud Connector") + - [ ] "back-end" (hyphenated, not "backend" or "back end") + - [ ] HTTP error code pairs use "and" not "/" (e.g. "HTTP 401 and HTTP 403") + +### 🔧 Technical Accuracy + +- [ ] **All links working** - Test external and internal links +- [ ] **Current year references** - Update to 2026 where appropriate +- [ ] **HTTPS URLs** - Use secure links when available +- [ ] **Complete code examples**: + - [ ] Proper language specification (```bash, ```json) + - [ ] No command prompts ($ symbols) in examples + - [ ] Valid syntax (especially JSON/YAML) + - [ ] Realistic example values +- [ ] **Configuration completeness**: + - [ ] All required properties shown + - [ ] No placeholder values like `` without explanation + - [ ] Working examples that can be copy-pasted + +### 📝 Formatting Consistency + +- [ ] **Heading capitalization**: + - [ ] Title case for H1, H2 (e.g., "Checklist for Support Tickets") + - [ ] Sentence case for H3+ (e.g., "Common deployment issues") + - [ ] Table of Contents label uses title case: "Table of Contents" + - [ ] No headings end with a question mark +- [ ] **List formatting**: + - [ ] Consistent bullet markers throughout, including the Table of Contents (prefer dashes `-`) + - [ ] Proper spacing after markers + - [ ] Parallel sentence structure + - [ ] Periods after complete sentences in list items +- [ ] **Link formatting**: + - [ ] Descriptive link text (not bare URLs) + - [ ] Proper markdown syntax `[text](url)` +- [ ] **Code block formatting**: + - [ ] Three backticks (not four) + - [ ] Language specified for syntax highlighting + - [ ] Proper indentation + +### 🔍 Detail Review (Based on KM Patterns) + +- [ ] **Punctuation corrections applied**: + - [ ] "certs" not "xerts" + - [ ] Proper semicolon/colon usage + - [ ] Correct quotation marks +- [ ] **Style improvements**: + - [ ] "For more information about" (not "around") + - [ ] "The best method is to" (not "For these purposes, its best you") + - [ ] Natural, conversational language +- [ ] **Technical precision**: + - [ ] Specific version numbers when relevant + - [ ] Accurate command syntax + - [ ] Complete error messages and solutions + +## Quality Validation + +### Minimum Standards +- [ ] **No critical errors** - All red ❌ items resolved +- [ ] **Quality score ≥ 75** - Run `docs-linter validate README.md --km-standards` +- [ ] **Working examples** - All code can be executed successfully +- [ ] **Complete sections** - No missing required content + +### Excellence Indicators +- [ ] **Quality score ≥ 90** - Meets KM excellence standards +- [ ] **Comprehensive coverage** - Addresses common user questions +- [ ] **Professional support information** - Clear escalation path for issues +- [ ] **Rich troubleshooting** - Covers common problems and solutions + +## File-Specific Considerations + +### README.md Files +- [ ] **Business context** - Clear explanation of what the sample/guide demonstrates +- [ ] **Prerequisites section** - All required tools, access, and knowledge +- [ ] **Step-by-step instructions** - Can be followed by a new user +- [ ] **Project structure** - File/folder organization explained +- [ ] **License information** - Appropriate licensing details + +### Technical Configuration Guides +- [ ] **Environment setup** - Complete environment requirements +- [ ] **Configuration examples** - Copy-pasteable configurations +- [ ] **Validation steps** - How to verify correct setup +- [ ] **Troubleshooting section** - Common issues and resolutions +- [ ] **Support checklist** - Information needed for support tickets + +### API Documentation +- [ ] **Authentication details** - How to authenticate requests +- [ ] **Complete examples** - Request and response samples +- [ ] **Error handling** - Error codes and resolution steps +- [ ] **Rate limiting** - Usage limitations and guidelines + +## Common Issues to Avoid + +Based on analysis of 30+ KM feedback commits: + +### Frequent Problems +- [ ] ❌ **Inconsistent bullet markers** (mixing -, *, +) +- [ ] ❌ **Vague link context** ("refer to this" instead of "see") +- [ ] ❌ **Missing language in code blocks** (```bash missing) +- [ ] ❌ **Placeholder text left in** ([TODO], [TBD]) +- [ ] ❌ **Broken internal links** (wrong anchor references) +- [ ] ❌ **Inconsistent terminology** (onpremise vs on-premise) + +### Quality Killers +- [ ] ❌ **Multiple H1 headings** (SEO and structure problems) +- [ ] ❌ **Skipped heading levels** (H1 → H3) +- [ ] ❌ **Passive voice overuse** ("should be configured" vs "configure") +- [ ] ❌ **Outdated version references** (2024/2025 instead of 2026) +- [ ] ❌ **Incomplete configurations** (missing required properties) + +## Pre-PR Testing + +### Local Validation +```bash +# Run comprehensive checks +docs-linter check README.md --comprehensive + +# Test auto-fixes (dry run first) +docs-linter fix README.md --dry-run +docs-linter fix README.md --safe-only + +# Final validation +docs-linter validate README.md --km-standards +``` + +### Manual Testing +- [ ] **Link testing** - Click all external links +- [ ] **Code execution** - Run all provided commands +- [ ] **Fresh perspective** - Can someone new follow the instructions? + +## Post-PR Actions + +After your PR is approved: +- [ ] **Monitor for feedback** - Address any post-merge issues quickly +- [ ] **Update related docs** - Consider if changes affect other documentation +- [ ] **Share learnings** - Contribute improvements back to this checklist + +## Quality Benchmarks + +### Target Scores +- **Minimum for PR approval**: 75/100 +- **Excellence target**: 90/100 +- **Gold standard examples**: 95+/100 + +### Quality Examples to Reference +1. `./misc/headless/fioriui/README.md` - Comprehensive technical guide +2. `./sample-fiori-gen-ext/README.md` - Well-structured sample documentation +3. `./misc/destinations/README.md` - Professional support and troubleshooting + +## Getting Help + +### Before Creating PR +- Review the [KM Style Guide](km-style-guide.md) +- Check [Golden Documentation Examples](golden-docs.md) +- Run the automated docs-linter validation + +### During Review Process +- Address all reviewer feedback promptly +- Use the docs-linter tool to validate fixes +- Ask for clarification on style questions + +### Support Resources +- KM Style Guide: Complete standards reference +- Docs-linter tool: Automated validation and fixes +- Quality examples: Real documentation that meets standards + +--- +*This checklist is based on analysis of 30+ KM feedback commits* +*Updated: January 2026 | Version 1.0* \ No newline at end of file diff --git a/docs/km-style-guide.md b/docs/km-style-guide.md index 4e1ab237..e49d5d10 100644 --- a/docs/km-style-guide.md +++ b/docs/km-style-guide.md @@ -215,7 +215,7 @@ Based on analysis of high-scoring documentation files: ### Automated Checks -The `docs-linter` tool is available on the `km-updates` branch only. Do not run these commands on `main`. +Use the `docs-linter` tool to automatically validate: ```bash # Validate a file diff --git a/docs/templates/troubleshooting.md b/docs/templates/troubleshooting.md new file mode 100644 index 00000000..e69de29b diff --git a/misc/headless/cap/README.md b/misc/headless/cap/README.md index df45f89a..1d75db80 100644 --- a/misc/headless/cap/README.md +++ b/misc/headless/cap/README.md @@ -100,4 +100,4 @@ yo @sap/fiori:headless ./cap_app_config.json --logLevel debug --skipInstall 1. If you have created a CAP project using an existing `managed` or `standalone` approuter configuration, then `addToManagedAppRouter` should be removed or set to `false`. ### License -Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file. +Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../../LICENSES/Apache-2.0.txt) file. diff --git a/misc/headless/fioriui/README.md b/misc/headless/fioriui/README.md index acbfa59d..e6b1dc92 100644 --- a/misc/headless/fioriui/README.md +++ b/misc/headless/fioriui/README.md @@ -87,4 +87,4 @@ yo @sap/fiori:headless ./app_config.json --logLevel debug --skipInstall ### License -Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file. +Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../../LICENSES/Apache-2.0.txt) file. diff --git a/misc/sslcerts/README.md b/misc/sslcerts/README.md index 3294298f..3a03f30a 100644 --- a/misc/sslcerts/README.md +++ b/misc/sslcerts/README.md @@ -1,6 +1,6 @@ # Handling Self-Signed SSL Certificates in SAP Fiori Tools -For more information on SSL certificates, please refer to the [SAP Help](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/4b318bede7eb4021a8be385c46c74045.html) and the [SAP Community](https://pages.community.sap.com/topics/fiori-tools). +For more information on SSL certificates, see the [SAP Help](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/4b318bede7eb4021a8be385c46c74045.html) and the [SAP Community](https://pages.community.sap.com/topics/fiori-tools). ## Overview @@ -37,7 +37,7 @@ Ignoring certificate errors might seem like a quick fix for development issues, ### Export the Certificate -To get a better understanding of how CA certificates work, please refer to the [Node.js documentation](https://nodejs.org/api/cli.html#node_extra_ca_certsfile). +To get a better understanding of how CA certificates work, see the [Node.js documentation](https://nodejs.org/api/cli.html#node_extra_ca_certsfile). 1. Navigate to the website using Edge, Chrome, or Firefox 1. Click on the padlock icon in the address bar @@ -89,7 +89,7 @@ export NODE_EXTRA_CA_CERTS=path/to/your/certificate.crt `NODE_TLS_REJECT_UNAUTHORIZED` is an environment variable in Node.js that controls SSL/TLS certificate validation behavior. -Setting `NODE_TLS_REJECT_UNAUTHORIZED=0` has the same security risks to `ignoreCertError`, please refer to the `Security Risk` section above. +Setting `NODE_TLS_REJECT_UNAUTHORIZED=0` has the same security risks as `ignoreCertError`. See the `Security Risk` section above. ```bash # WARNING: Only for development environments diff --git a/neo-migration/README.md b/neo-migration/README.md index 978a7949..30f4936f 100644 --- a/neo-migration/README.md +++ b/neo-migration/README.md @@ -117,7 +117,7 @@ Please note, this destination is creating destinations at `subaccount` level, al Security configuration is configured using a global role collection that can be consumed by apps using the mta ID and the scoped name i.e. `migrationcf.globalrole`. In this instance, its only for demo purposes and the respective applications will manage their own security concerns, creating their own roles/templates in the `xs-security.json` attached to the project. -For more information around Security Administration, refer to the [SAP BTP Security Administration Guide](). +For more information about Security Administration, see the [SAP BTP Security Administration Guide](). Ensure you are logged into CF target system where the new settings need to be applied: diff --git a/sample-fiori-gen-ext/README.md b/sample-fiori-gen-ext/README.md index b49447ab..58735a89 100644 --- a/sample-fiori-gen-ext/README.md +++ b/sample-fiori-gen-ext/README.md @@ -268,4 +268,5 @@ For additional help please reach out using our Community Forum: [SAP Fiori tools Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../LICENSES/Apache-2.0.txt) file. - +### License +Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../LICENSES/Apache-2.0.txt) file. diff --git a/scripts/extract-km-patterns.js b/scripts/extract-km-patterns.js new file mode 100755 index 00000000..7d2d22ad --- /dev/null +++ b/scripts/extract-km-patterns.js @@ -0,0 +1,425 @@ +#!/usr/bin/env node + +/** + * Git History Analysis Script for KM Feedback Pattern Extraction + * + * This script analyzes git history to extract Knowledge Management (KM) feedback patterns + * from commits that contain documentation updates and improvements. + */ + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +class KMPatternExtractor { + constructor() { + this.patterns = { + formatting: [], + content: [], + structure: [], + technical: [] + }; + this.corrections = { + typos: {}, + terminology: {}, + punctuation: {} + }; + this.qualityExamples = []; + } + + /** + * Extract KM feedback commits from git history + */ + extractKMCommits() { + console.log('📊 Extracting KM feedback commits from git history...'); + + const kmCommitPatterns = [ + 'Apply suggestions', + 'text updates', + 'documentation updates', + 'fix: update', + 'updates based on', + 'cleanup text', + 'append updates' + ]; + + const commits = []; + + for (const pattern of kmCommitPatterns) { + try { + const result = execSync(`git log --oneline --grep="${pattern}" -n 20`, { encoding: 'utf8' }); + const lines = result.trim().split('\n').filter(line => line.trim()); + + for (const line of lines) { + const [hash, ...messageParts] = line.split(' '); + const message = messageParts.join(' '); + + if (hash && !commits.find(c => c.hash === hash)) { + commits.push({ hash, message, pattern }); + } + } + } catch (error) { + console.warn(`Warning: Could not extract commits for pattern "${pattern}"`); + } + } + + console.log(`Found ${commits.length} KM feedback commits`); + return commits; + } + + /** + * Analyze a commit to extract patterns and improvements + */ + analyzeCommit(commit) { + try { + const diffOutput = execSync(`git show ${commit.hash} --color=never`, { encoding: 'utf8' }); + const filesChanged = execSync(`git show ${commit.hash} --name-only --pretty=format:`, { encoding: 'utf8' }); + + const files = filesChanged.trim().split('\n').filter(f => f.endsWith('.md')); + + if (files.length === 0) return; + + const analysis = { + commit: commit.hash, + message: commit.message, + files: files, + changes: this.extractChanges(diffOutput), + patterns: this.identifyPatterns(diffOutput) + }; + + this.categorizePatterns(analysis); + return analysis; + + } catch (error) { + console.warn(`Warning: Could not analyze commit ${commit.hash}`); + return null; + } + } + + /** + * Extract before/after changes from git diff + */ + extractChanges(diffOutput) { + const changes = []; + const lines = diffOutput.split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + if (line.startsWith('-') && !line.startsWith('---') && !line.startsWith('diff')) { + const before = line.substring(1).trim(); + const nextLine = lines[i + 1]; + + if (nextLine && nextLine.startsWith('+') && !nextLine.startsWith('+++')) { + const after = nextLine.substring(1).trim(); + + if (before !== after && before.length > 0 && after.length > 0) { + changes.push({ before, after, type: this.classifyChange(before, after) }); + } + } + } + } + + return changes; + } + + /** + * Classify the type of change based on before/after content + */ + classifyChange(before, after) { + // Typo corrections + if (this.isTypoCorrection(before, after)) { + return 'typo'; + } + + // Formatting changes + if (this.isFormattingChange(before, after)) { + return 'formatting'; + } + + // Structural changes + if (this.isStructuralChange(before, after)) { + return 'structural'; + } + + // Content improvements + if (this.isContentImprovement(before, after)) { + return 'content'; + } + + return 'other'; + } + + /** + * Check if change is a typo correction + */ + isTypoCorrection(before, after) { + const beforeWords = before.toLowerCase().split(/\s+/); + const afterWords = after.toLowerCase().split(/\s+/); + + if (beforeWords.length !== afterWords.length) return false; + + let differences = 0; + for (let i = 0; i < beforeWords.length; i++) { + if (beforeWords[i] !== afterWords[i]) { + differences++; + // Store typo correction + if (differences === 1) { + this.corrections.typos[beforeWords[i]] = afterWords[i]; + } + } + } + + return differences === 1; + } + + /** + * Check if change is formatting-related + */ + isFormattingChange(before, after) { + const formatPatterns = [ + /^#+\s/, // Headers + /^[-*+]\s/, // Lists + /^\d+\.\s/, // Numbered lists + /\*\*.*\*\*/, // Bold + /`.*`/, // Code + /\[.*\]\(.*\)/ // Links + ]; + + return formatPatterns.some(pattern => + pattern.test(before) || pattern.test(after) + ); + } + + /** + * Check if change is structural + */ + isStructuralChange(before, after) { + const structuralKeywords = [ + 'table of contents', 'overview', 'prerequisites', 'conclusion', + 'getting started', 'introduction', 'summary', 'checklist' + ]; + + return structuralKeywords.some(keyword => + before.toLowerCase().includes(keyword) || after.toLowerCase().includes(keyword) + ); + } + + /** + * Check if change is content improvement + */ + isContentImprovement(before, after) { + // Content improvements typically make text clearer, more specific, or more accurate + return after.length > before.length || + after.split(' ').length > before.split(' ').length || + after.includes('such as') || after.includes('for example') || + after.includes('Note:') || after.includes('Important:'); + } + + /** + * Identify high-level patterns in the diff + */ + identifyPatterns(diffOutput) { + const patterns = []; + + if (diffOutput.includes('# ') || diffOutput.includes('## ')) { + patterns.push('header_formatting'); + } + + if (diffOutput.includes('- ') || diffOutput.includes('* ')) { + patterns.push('list_formatting'); + } + + if (diffOutput.includes('```') || diffOutput.includes('`')) { + patterns.push('code_formatting'); + } + + if (diffOutput.includes('[') && diffOutput.includes('](')) { + patterns.push('link_formatting'); + } + + return patterns; + } + + /** + * Categorize patterns into the main categories + */ + categorizePatterns(analysis) { + for (const change of analysis.changes) { + switch (change.type) { + case 'formatting': + this.patterns.formatting.push({ + ...change, + commit: analysis.commit, + file: analysis.files[0] + }); + break; + case 'content': + this.patterns.content.push({ + ...change, + commit: analysis.commit, + file: analysis.files[0] + }); + break; + case 'structural': + this.patterns.structure.push({ + ...change, + commit: analysis.commit, + file: analysis.files[0] + }); + break; + case 'typo': + this.patterns.technical.push({ + ...change, + commit: analysis.commit, + file: analysis.files[0] + }); + break; + } + } + } + + /** + * Analyze files that received minimal KM feedback as quality examples + */ + identifyQualityExamples() { + console.log('🏆 Identifying quality documentation examples...'); + + try { + // Find README files that haven't been modified recently + const readmeFiles = execSync('find . -name "README.md" -type f', { encoding: 'utf8' }) + .trim().split('\n').filter(f => f); + + for (const file of readmeFiles) { + try { + const commitCount = execSync(`git log --oneline "${file}" | wc -l`, { encoding: 'utf8' }); + const recentChanges = execSync(`git log --oneline --since="6 months ago" "${file}" | wc -l`, { encoding: 'utf8' }); + + const totalCommits = parseInt(commitCount.trim()); + const recentCommits = parseInt(recentChanges.trim()); + + // Files with few recent changes might be high quality + if (totalCommits > 3 && recentCommits < 3) { + const fileSize = fs.statSync(file).size; + if (fileSize > 5000) { // Substantial documentation + this.qualityExamples.push({ + file: file, + totalCommits: totalCommits, + recentCommits: recentCommits, + size: fileSize, + score: this.calculateQualityScore(totalCommits, recentCommits, fileSize) + }); + } + } + } catch (error) { + // Skip files that cause errors + } + } + + // Sort by quality score + this.qualityExamples.sort((a, b) => b.score - a.score); + console.log(`Found ${this.qualityExamples.length} quality examples`); + + } catch (error) { + console.warn('Warning: Could not identify quality examples'); + } + } + + /** + * Calculate quality score for documentation files + */ + calculateQualityScore(totalCommits, recentCommits, fileSize) { + // Higher score = better quality + // Factors: established (totalCommits), stable (few recent changes), substantial (fileSize) + return (totalCommits * 2) + (10 - recentCommits) + (fileSize / 1000); + } + + /** + * Save training data to JSON files + */ + saveTrainingData() { + console.log('💾 Saving training data...'); + + const trainingDir = path.join(process.cwd(), 'training-data'); + + // Save pattern data + fs.writeFileSync( + path.join(trainingDir, 'km-feedback-patterns.json'), + JSON.stringify(this.patterns, null, 2) + ); + + // Save corrections dictionary + fs.writeFileSync( + path.join(trainingDir, 'correction-dictionary.json'), + JSON.stringify(this.corrections, null, 2) + ); + + // Save quality examples + fs.writeFileSync( + path.join(trainingDir, 'quality-examples.json'), + JSON.stringify(this.qualityExamples, null, 2) + ); + + console.log('✅ Training data saved to training-data/ directory'); + } + + /** + * Generate summary report + */ + generateSummary() { + console.log('\n📋 KM Pattern Extraction Summary'); + console.log('================================'); + console.log(`Formatting patterns: ${this.patterns.formatting.length}`); + console.log(`Content patterns: ${this.patterns.content.length}`); + console.log(`Structural patterns: ${this.patterns.structure.length}`); + console.log(`Technical patterns: ${this.patterns.technical.length}`); + console.log(`Typo corrections: ${Object.keys(this.corrections.typos).length}`); + console.log(`Quality examples: ${this.qualityExamples.length}`); + + console.log('\n🔝 Top Quality Examples:'); + this.qualityExamples.slice(0, 5).forEach((example, index) => { + console.log(`${index + 1}. ${example.file} (score: ${example.score.toFixed(1)})`); + }); + } + + /** + * Main execution method + */ + async run() { + console.log('🚀 Starting KM Pattern Extraction...\n'); + + // Extract commits + const commits = this.extractKMCommits(); + + // Analyze each commit + console.log('🔍 Analyzing commits for patterns...'); + for (const commit of commits) { + const analysis = this.analyzeCommit(commit); + if (analysis) { + console.log(`Analyzed: ${commit.hash} - ${commit.message}`); + } + } + + // Identify quality examples + this.identifyQualityExamples(); + + // Save training data + this.saveTrainingData(); + + // Generate summary + this.generateSummary(); + + console.log('\n🎉 KM pattern extraction completed successfully!'); + } +} + +// Run the extractor if called directly +if (require.main === module) { + const extractor = new KMPatternExtractor(); + extractor.run().catch(error => { + console.error('❌ Error during extraction:', error.message); + process.exit(1); + }); +} + +module.exports = KMPatternExtractor; \ No newline at end of file diff --git a/thirdpartylibrary/README.md b/thirdpartylibrary/README.md index f61be036..4b1e75bf 100644 --- a/thirdpartylibrary/README.md +++ b/thirdpartylibrary/README.md @@ -8,3 +8,6 @@ # Sample Project The [freestyle SAPUI5 application](./ztravelapp/README.md) demonstrates how to add an external third party library `xml-js` to the project. + +### License +Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../LICENSES/Apache-2.0.txt) file. diff --git a/thirdpartylibrary/ztravelapp/README.md b/thirdpartylibrary/ztravelapp/README.md index e1fad241..d9273e61 100644 --- a/thirdpartylibrary/ztravelapp/README.md +++ b/thirdpartylibrary/ztravelapp/README.md @@ -14,7 +14,7 @@ From your SAP BTP cockpit, select Instances and Subscriptions, select SAP Busine # Generate a freestyle SAPUI5 application -Please refer to the following link, [Developing Apps with SAP Fiori Tools](https://sapui5.hana.ondemand.com/sdk/#/topic/a460a7348a6c431a8bd967ab9fb8d918) for more information. +For more information, see [Developing Apps with SAP Fiori Tools](https://sapui5.hana.ondemand.com/sdk/#/topic/a460a7348a6c431a8bd967ab9fb8d918). # Tasks @@ -42,7 +42,7 @@ sap.ui.define( ["sap/ui/core/mvc/Controller", "xml-js"], ``` -Understanding the `sap.ui.defined` JavaScript namespace, please refer to this [link](https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui%23methods/sap.ui.define) paying attention to the `Third Party Modules` section, as this provides guidance on how third party modules are imported and referenced. +To understand the `sap.ui.defined` JavaScript namespace, see this [link](https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui%23methods/sap.ui.define), paying attention to the `Third Party Modules` section, as this provides guidance on how third party modules are imported and referenced. Next, update the `function` to reference this library; ```JS diff --git a/training-data/correction-dictionary.json b/training-data/correction-dictionary.json new file mode 100644 index 00000000..ddbf5048 --- /dev/null +++ b/training-data/correction-dictionary.json @@ -0,0 +1,25 @@ +{ + "typos": { + "xerts)?": "certs)?", + "4.": "-", + "user’s": "user's", + "screenshots": "provide", + "`oauth2clientcredentials`;": "`oauth2clientcredentials`.", + "are": "do", + "information;": "information:", + "1.": "1:", + "2.": "2:", + "2009-2025": "2009-2026", + "enable": "[trace", + "ecc.": "ecc", + "btp,": "btp", + "provide": "required", + "````": "```", + "onpremise": "on-premise", + "(onpremise)": "(on-premise)", + "ui": "ui.", + "destinations](https://learning.sap.com/learning-journeys/administrating-sap-business-technology-platform/using-destinations)": "destinations](https://learning.sap.com/courses/operating-sap-business-technology-platform/using-destinations)" + }, + "terminology": {}, + "punctuation": {} +} \ No newline at end of file diff --git a/training-data/km-feedback-patterns.json b/training-data/km-feedback-patterns.json new file mode 100644 index 00000000..158bd5b8 --- /dev/null +++ b/training-data/km-feedback-patterns.json @@ -0,0 +1,786 @@ +{ + "formatting": [ + { + "before": "# Support ticket checklist", + "after": "# Checklist for Support Tickets", + "type": "formatting", + "commit": "a125c1a", + "file": "misc/onpremise/README.md" + }, + { + "before": "- Clear reproduction steps and expected versus actual behavior", + "after": "- `curl` output from an SAP Business Application Studio terminal when executing the connection test. See the example below.", + "type": "formatting", + "commit": "a125c1a", + "file": "misc/onpremise/README.md" + }, + { + "before": "Before addressing any issues with deployment, ensure connectivity is working as per the [Validate connectivity](#validate-connectivity) section.", + "after": "Before addressing any issues with deployment, ensure connectivity is working as per the [Validate Connectivity](#validate-connectivity) section.", + "type": "formatting", + "commit": "a125c1a", + "file": "misc/onpremise/README.md" + }, + { + "before": "* Access Control: SCC -> Cloud to On-Premise -> Access Control -> Select Mapping -> Actions -> Edit (pencil icon).", + "after": "* Virtual Host Mapping: SAP Cloud Connector -> Cloud to On-Premise -> Select Virtual Host Mapping as defined in the SAP BTP destination.", + "type": "formatting", + "commit": "5a1479b", + "file": "misc/onpremise/README.md" + }, + { + "before": "- `WebIDEEnabled` is set to true; this means that the destination is enabled for use in the SAP Business Application Studio", + "after": "- `WebIDEEnabled` is set to true. This means that the destination is enabled for use in SAP Business Application Studio.", + "type": "formatting", + "commit": "c1ed6af", + "file": "misc/onpremise/README.md" + }, + { + "before": "For these purposes, its best you clone your existing SAP BTP destination, and change the type to a partial URL destination. This allows you to specify the `Service URL` as the base URL for the OData V2 or V4 catalog, and then append the specific service path to the destination URL.", + "after": "The best method is to clone your existing SAP BTP destination and change the type to a partial URL destination. This allows you to specify the `Service URL` as the base URL for the OData V2 or V4 catalog, and then append the specific service path to the destination URL.", + "type": "formatting", + "commit": "09e9f2a", + "file": "misc/s4hana/README.md" + }, + { + "before": "### Issue 6. Standard OData services not showing in RecommendedServiceCollection", + "after": "### Issue 6: Standard OData services Are Not Displayed in `RecommendedServiceCollection`", + "type": "formatting", + "commit": "09e9f2a", + "file": "misc/s4hana/README.md" + }, + { + "before": "### Issue 7: There Are No OData services Available in the OData V2 Catalog", + "after": "### Issue 7: There Are No OData Services Available in the OData V2 Catalog", + "type": "formatting", + "commit": "09e9f2a", + "file": "misc/s4hana/README.md" + }, + { + "before": "## Step 1: Screenshots Required", + "after": "## Step 1: Provide Screenshots", + "type": "formatting", + "commit": "a3cc102", + "file": "misc/onpremise/README.md" + }, + { + "before": "- The CAP project and Fiori UI application are deployed to Cloud Foundry", + "after": "- HANA Cloud database is setup and running in your cloud space. Refer to this [tutorial](https://developers.sap.com/tutorials/hana-cloud-create-db-project.html).", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "## Step 4: Apply security to Catalog Service", + "after": "## Step 4: Apply Security to Catalog Service", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "For more information around destinations, refer to this [blog post](https://community.sap.com/t5/technology-blogs-by-members/sap-btp-destinations-in-a-nutshell-part-3-oauth-2-0-client-credentials/ba-p/13577101).", + "after": "For more information about destinations, see this [blog post](https://community.sap.com/t5/technology-blogs-by-members/sap-btp-destinations-in-a-nutshell-part-3-oauth-2-0-client-credentials/ba-p/13577101).", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "- `CloudConnectorLocationId` is set to `scloud`, this is the location ID of the SAP Cloud Connector that is configured in the SAP BTP cockpit, SAP BTP subaccount can be configured with different Cloud Connectors", + "after": "- `HTML5.DynamicDestination` is set to true. This means that the destination will be dynamically created at runtime.", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "For more details on these logs, please refer to SAP Cloud Connector Troubleshooting https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/cloud-connector-troubleshooting.", + "after": "For more details about these logs, see [SAP Cloud Connector Troubleshooting](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/cloud-connector-troubleshooting).", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "If you are not seeing any network traffic in the `traffic_trace_` logs, then the issues are most likely with the SCC configuration where the SAP Cloud Connector is unable to establish a secure connection to the target ABAP system which is blocking traffic.", + "after": "If you do not see any network traffic in the `traffic_trace_` logs, then the issue is most likely with the SCC configuration where the SAP Cloud Connector is unable to establish a secure connection to the target ABAP system which is blocking traffic.", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "* Access Control: SCC -> Cloud to On-Premise -> Access Control -> Select Mapping -> Ensure Access Policy is set to Path and All Sub-Paths and URL Path is / (this might differ depending on security concerns)", + "after": "* Access Control: SCC -> Cloud to On-Premise -> Access Control -> Select Mapping -> Ensure Access Policy is set to Path and All Sub-Paths and URL Path is /. Note this may differ depending on security concerns.", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "* Set Cloud Connector Loggers to ALL", + "after": "* Confirm the version of your SAP Cloud Connector.", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "If you are experiencing deployment issues, please refer to the [Deployment Issues](https://ga.support.sap.com/index.html#/tree/3046/actions/45995:45996:50742:46000) guide for troubleshooting steps.", + "after": "If you are experiencing deployment issues, see [Deployment Issues](https://ga.support.sap.com/index.html#/tree/3046/actions/45995:45996:50742:46000) for troubleshooting steps.", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "Re-run the deployment command `npm run deploy` and check the console output for any errors or issues. The trace logging will provide detailed information about the requests and responses between the SAP BTP and the on-premise system.", + "after": "Re-run the deployment command `npm run deploy` and check the console output for any errors or issues. The trace logging will provide detailed information about the requests and responses between the SAP BTP and the On-Premise system.", + "type": "formatting", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "For more information about connectivity issues related to principal propagation configurations, see [How to Troubleshoot Cloud Connector Principal Propagation over HTTPS](https://help.sap.com/docs/SUPPORT_CONTENT/appservices/3361376259.html#HowtotroubleshootCloudConnectorprincipalpropagationoverHTTPS-Checkingthelogs,followtheclientcertificate).", + "after": "For more information about connectivity issues related to principal propagation configurations and to trace connectivity issues, see [How to Troubleshoot Cloud Connector Principal Propagation over HTTPS](https://help.sap.com/docs/SUPPORT_CONTENT/appservices/3361376259.html#HowtotroubleshootCloudConnectorprincipalpropagationoverHTTPS-Checkingthelogs,followtheclientcertificate).", + "type": "formatting", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "- `Authentication` is set to `NoAuthentication`. This means that the destination does not require authentication.", + "after": "- `Authentication` is set to `NoAuthentication`. This means that the destination does not require authentication. Endpoints that require authentication will need to be configured with the appropriate authentication type, such as `BasicAuthentication`, `OAuth2ClientCredentials`, etc.", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "Note: `https://dest.` can also be replaced with `$H2O_URL/destinations//`, for example.", + "after": "Note: `https://.dest/` is a placeholder that is appended with the name of your destination. It routes the HTTP request using the BAS proxy and sets up the connection to your API back end.", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "1. Click `Save`. Ensure you have the client secret, if required.", + "after": "# Common Errors", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "```", + "after": "## Issue One", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "```", + "after": "__Issue: Getting HTTP 4** Exceptions When Calling the Destination__", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "```", + "after": "The URL property of a SAP BTP destination must contain only the base host and root service path.", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "# Common Errors", + "after": "**HTTP 404 Not Found** (most common)", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "## Issue One", + "after": "**HTTP 401/403** (when authentication is attempted against an invalid path)", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "__Issue: Getting HTTP 4** Exceptions When Calling the Destination__", + "after": "## Example of an Incorrect Destination URL", + "type": "formatting", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "If problems persist, follow the Enable Trace Logging steps below to gather logs and re-run the environment check.", + "after": "If problems persist, follow the [trace logging](./README.md#enable-cloud-connector-trace-logging) steps below to gather logs and re-run the [Environment Check report](../destinations/README.md#environment-check).", + "type": "formatting", + "commit": "e886eeb", + "file": "misc/onpremise/README.md" + }, + { + "before": "- From your SAP Cloud Connector:", + "after": "1. Screenshot of the destination in the SAP BTP cockpit (show all properties)", + "type": "formatting", + "commit": "e886eeb", + "file": "misc/onpremise/README.md" + }, + { + "before": "- Output from ABAP traces `/IWFND/ERROR_LOG` and `/IWFND/GW_CLIENT`. For more information, see [SAP ABAP guide](https://www.youtube.com/watch?v=Tmb-O966GwM).", + "after": "- Collected logs from trace logging (see list above)", + "type": "formatting", + "commit": "e886eeb", + "file": "misc/onpremise/README.md" + }, + { + "before": "curl -vs -i -H \"X-CSRF-Token: Fetch\" \"https://.dest/sap/opu/odata/UI5/ABAP_REPOSITORY_SRV/Repositories(%27%27)?saml2=disabled\" > curl-abap-srv-output.txt 2>&1", + "after": "# Replace before executing", + "type": "formatting", + "commit": "e886eeb", + "file": "misc/onpremise/README.md" + }, + { + "before": "- `URL` is set to your Cloud Connector internal host, for example `http://my-internal-host:44330/`. This indicates the internal URL that is mapped to your On-Premise ABAP system within your local network. The URL always defaults to `http://` so only the port and address are configurable.", + "after": "- `URL` is set to your Cloud Connector internal host, for example `http://my-internal-host:44330/`. This indicates the internal URL that is mapped to your on-premise ABAP system within your local network. The URL always defaults to `http://` so only the port and address are configurable.", + "type": "formatting", + "commit": "9978b2f", + "file": "misc/onpremise/README.md" + }, + { + "before": "- Ensure that the SAP Cloud Connector is running and that the connection to the On-Premise system is established. You can review the Cloud Connector logs for errors. For more information, see [Enable Tracing Logging](./README.md#step-2-enable-trace-logging).", + "after": "- Ensure that the SAP Cloud Connector is running and that the connection to the on-premise system is established. You can review the SAP Cloud Connector logs for errors. For more information, see [Enable Tracing Logging](./README.md#step-2-enable-trace-logging).", + "type": "formatting", + "commit": "9978b2f", + "file": "misc/onpremise/README.md" + }, + { + "before": "* `traffic_trace__on_.trc`", + "after": "* `traffic_trace__on_.trc` (required)", + "type": "formatting", + "commit": "9978b2f", + "file": "misc/onpremise/README.md" + }, + { + "before": "An SAP BTP destination defined with the proxy type: `OnPremise` is a configuration that enables secure connectivity between your SAP Business Technology Platform (BTP) applications and On-Premise systems residing behind your corporate firewall.", + "after": "An SAP BTP destination defined with the proxy type: `OnPremise` is a configuration that enables secure connectivity between your SAP Business Technology Platform (BTP) applications and on-premise systems residing behind your corporate firewall.", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "Authentication options include:", + "after": "1. Authentication options include;", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "- Only OData XML services are supported when creating SAP Fiori elements applications with the SAP Fiori generator.", + "after": "- You have admin rights to the local SAP Cloud Connector UI.", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "This guide does not document the steps to configure a Cloud Connector. For more information about how to configure a Cloud Connector, see [Installation and Configuration of SAP Cloud Connector](https://blogs.sap.com/2021/09/05/installation-and-configuration-of-sap-cloud-connector).", + "after": "For more information about how to configure an SAP Cloud Connector, see [Installation and Configuration of SAP Cloud Connector](https://blogs.sap.com/2021/09/05/installation-and-configuration-of-sap-cloud-connector).", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "This [guide](https://ga.support.sap.com/dtp/viewer/index.html#/tree/3046/actions/45995:48363:53594:48366:52526) covers some of the most common issues encountered when using Cloud Connector and SAP BTP destinations. If you make changes to your configuration, re-run the steps to see if the issue is resolved.", + "after": "This [guide](https://ga.support.sap.com/dtp/viewer/index.html#/tree/3046/actions/45995:48363:53594:48366:52526) covers some of the most common issues encountered when using SAP Cloud Connector and SAP BTP destinations. If you make changes to your configuration, re-run the steps to see if the issue is resolved.", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "- Ensure there are no issues with firewalls or proxies blocking incoming connections from SAP BTP. You may need to whitelist the IP addresses of the SAP BTP data center. For more information, see [2682913 - Cloud Connector](https://me.sap.com/notes/0002682913).", + "after": "- Ensure that the SAP Cloud Connector is running and that the connection to the On-Premise system is established. You can review the Cloud Connector logs for errors. For more information, see [Enable Tracing Logging](./README.md#step-2-enable-trace-logging).", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "If you do not see any network traffic in the `traffic_trace_` logs, then the issue is most likely with the SAP Cloud Connector configuration where the SAP Cloud Connector is unable to establish a secure connection to the target ABAP system. In most cases, this is related to a local firewall or proxy, blocking requests. For more information, see [Invalid proxy response status: 503 Service Unavailable](https://ga.support.sap.com/index.html#/tree/3046/actions/45995:48363:53594:63697:48366:52526). This requires support from your IT Admin team.", + "after": "If you do not see any network traffic in the `traffic_trace_` logs, then the issue is most likely with the SAP Cloud Connector configuration where the connector is unable to establish a secure connection to the target ABAP system. In most cases, this is related to a local firewall or proxy, blocking requests. For more information, see [Invalid proxy response status: 503 Service Unavailable](https://ga.support.sap.com/index.html#/tree/3046/actions/45995:48363:53594:63697:48366:52526). This requires support from your IT Admin team.", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "For more information about connectivity issues related to Principal Propagation configurations, see [How to troubleshoot Cloud Connector principal propagation over HTTPS](https://help.sap.com/docs/SUPPORT_CONTENT/appservices/3361376259.html#HowtotroubleshootCloudConnectorprincipalpropagationoverHTTPS-Checkingthelogs,followtheclientcertificate).", + "after": "For more information about connectivity issues related to Principal Propagation configurations, see [How to troubleshoot SAP Cloud Connector principal propagation over HTTPS](https://help.sap.com/docs/SUPPORT_CONTENT/appservices/3361376259.html#HowtotroubleshootCloudConnectorprincipalpropagationoverHTTPS-Checkingthelogs,followtheclientcertificate).", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "[Consuming SAPUI5 Libraries from an On-Premise System](./ui5-onpremise.md) provides a step-by-step guide to consuming SAPUI5 libraries from an On-Premise system using SAP Cloud Connector and a SAP BTP destination.", + "after": "[Consuming SAPUI5 Libraries from an On-Premise System](./ui5-onpremise.md) provides a step-by-step guide to consuming SAPUI5 libraries from an on-premise system using SAP Cloud Connector and a SAP BTP destination.", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "Re-run the deployment command `npm run deploy` and check the console output for any errors or issues. The trace logging provides detailed information about the requests and responses between the SAP BTP and the On-Premise system.", + "after": "Re-run the deployment command `npm run deploy` and check the console output for any errors or issues. The trace logging provides detailed information about the requests and responses between the SAP BTP and the on-premise system.", + "type": "formatting", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "* Confirm the version of your SAP Cloud Connector (SCC).", + "after": "* Login to the SCC UI.", + "type": "formatting", + "commit": "1be0710", + "file": "misc/onpremise/README.md" + }, + { + "before": "If you do not see any network traffic in the `traffic_trace_` logs, then the issue is most likely with the SCC configuration where the SAP Cloud Connector is unable to establish a secure connection to the target ABAP system which is blocking traffic.", + "after": "If you do not see any network traffic in the `traffic_trace_` logs, then the issue is most likely with the SCC configuration where the SAP Cloud Connector is unable to establish a secure connection to the target ABAP system. In most cases, this is related to a local firewall or proxy, blocking requests, refer to this [guide](https://ga.support.sap.com/index.html#/tree/3046/actions/45995:48363:53594:63697:48366:52526) which will require support from your IT Admin team.", + "type": "formatting", + "commit": "1be0710", + "file": "misc/onpremise/README.md" + }, + { + "before": "Also, specify the date and time of the request to help us narrow down the logs.", + "after": "## Step 2: Enable Trace Logging", + "type": "formatting", + "commit": "1be0710", + "file": "misc/onpremise/README.md" + }, + { + "before": "Once you've gathered the logs, you can disable the trace settings.", + "after": "Refer to the section [Enable Trace Logging](#enable-trace-logging) and provide all the requested log files.", + "type": "formatting", + "commit": "1be0710", + "file": "misc/onpremise/README.md" + }, + { + "before": "Unless you have a specific technical reason, the default should be `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress` as the `nameIdFormat`. The email address as defined in your IdP i.e OpenID Connect or IAS must match the S4HC email address configured with the appropriate roles. Please refer to the related links section below to understand more around adding other IdP's to your SAB BTP system.", + "after": "Notes;", + "type": "formatting", + "commit": "1678180", + "file": "misc/s4hana/README.md" + }, + { + "before": "2. After running a `curl` command or the Environment Check report, all requests are failing with HTTP 500 but they are not hitting your S4HC instance. Your SAP BTP destination may be corrupted. Clone the existing destination and use the new destination in your SAP Business Application Studio instance.", + "after": "2. If the `nameIdFormat` in your SAP BTP destination is set to `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`, ensure the email address in your Identity Provider (IdP) matches the email address configured for your user in your S4HC instance.", + "type": "formatting", + "commit": "1678180", + "file": "misc/s4hana/README.md" + }, + { + "before": "[![Alt text](Step2b.png \"Catalog of services\")](Step2b.png)", + "after": "[![Alt text](Step2b.png \"Catalog of Services\")](Step2b.png)", + "type": "formatting", + "commit": "bb42511", + "file": "cap/destination/README.md" + }, + { + "before": "Step1. Access your `nodejs` service, selecting your dev space, which will list all the running services on your space;", + "after": "Step 1. Access your `nodejs` service, selecting your dev space, which will list all the running services on your space;", + "type": "formatting", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "Step4. Select the `Authorization and Trust Management Service` service instance that was deployed with your CAP project, in this case, `managedAppCAPProject-xsuaa-service`;", + "after": "Step 4. Select the `Authorization and Trust Management Service` service instance that was deployed with your CAP project, in this case, `managedAppCAPProject-xsuaa-service`;", + "type": "formatting", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "Step5. Select the `Service Keys` tab, if a key doesn't exist, create a new service key;", + "after": "Step 5. Select the `Service Keys` tab, if a key doesn't exist, create a new service key;", + "type": "formatting", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "Step6. Create a new destination in your SAP BTP account, navigate to the `Connectivity` service and select `Destinations` and `Create destination` and change the `Authentication` type to `OAuth2ClientCredentials`;", + "after": "Step 6. Create a new destination in your SAP BTP account, navigate to the `Connectivity` service and select `Destinations` and `Create destination` and change the `Authentication` type to `OAuth2ClientCredentials`;", + "type": "formatting", + "commit": "f9c170f", + "file": "cap/destination/README.md" + } + ], + "content": [ + { + "before": "These changes required to reduce the amount of manual tasks and will hopefully be incorporated into a future edition of the SAP Fiori tools deployment generator.", + "after": "These changes are required to reduce the number of manual tasks. They will hopefully be incorporated into a future edition of the SAP Fiori tools deployment generator.", + "type": "content", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "Understanding approuter more, follow these links;", + "after": "To learn more about approuter, see the following links:", + "type": "content", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "Append support for the different OAuth endpoints, for local development with VSCode, Business Application Studio and SAP BTP Cloud Foundry;", + "after": "Append support for the different OAuth endpoints, for local development with Visual Studio Code, SAP Business Application Studio and SAP BTP Cloud Foundry:", + "type": "content", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "If connectivity to the ABAP system is working but API requests are failing, check the ABAP transaction logs:", + "after": "If the connection to the ABAP system is working but API requests are failing, check the ABAP transaction logs:", + "type": "content", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "curl \"https://northwind.dest/v2/northwind/northwind.svc/\" -vs > curl-datasrv-output.txt 2>&1", + "after": "curl -L \"https://northwind.dest/v2/northwind/northwind.svc/\" -vs > curl-datasrv-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl \"https://northwind.dest/v2/northwind/northwind.svc/\\$metadata\" -vs > curl-datasrv-meta-output.txt 2>&1", + "after": "curl -L \"https://northwind.dest/v2/northwind/northwind.svc/\\$metadata\" -vs > curl-datasrv-meta-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl \"https://.dest/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection\" -vs > curl-v2catalog-output.txt 2>&1", + "after": "curl -L \"https://.dest/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection\" -vs > curl-v2catalog-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl \"https://.dest/sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002/ServiceGroups?\\$expand=DefaultSystem(\\$expand=Services)\" -vs > curl-v4catalog-output.txt 2>&1", + "after": "curl -L \"https://.dest/sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002/ServiceGroups?\\$expand=DefaultSystem(\\$expand=Services)\" -vs > curl-v4catalog-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "For example:", + "after": "then SAP Fiori tools or the Service Center will automatically append the service path required for the operation.", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "__Solution__", + "after": "With this configuration, the destination URL is treated as a full URL, and no additional paths or parameters are appended by SAP Fiori tools or the Service Center.", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl \"https://northwind_fullurl.dest/\" -vs > curl-fullurl-output.txt 2>&1", + "after": "curl -L \"https://northwind_fullurl.dest/\" -vs > curl-fullurl-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl \"https://northwind_fullurl.dest/\\$metadata\" -vs > curl-fullurl-meta-output.txt 2>&1", + "after": "curl -L \"https://northwind_fullurl.dest/\\$metadata\" -vs > curl-fullurl-meta-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl -vs -i -H \"X-CSRF-Token: Fetch\" \"https://.dest/sap/opu/odata/IWFND/CATALOGSERVICE;v=2?saml2=disabled\" > curl-catalog-output.txt 2>&1", + "after": "curl -L -vs -i -H \"X-CSRF-Token: Fetch\" \"https://.dest/sap/opu/odata/IWFND/CATALOGSERVICE;v=2?saml2=disabled\" > curl-catalog-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl -vs -i -H \"X-CSRF-Token: Fetch\" \"https://.dest/sap/opu/odata/UI5/ABAP_REPOSITORY_SRV/Repositories(%27%27)?saml2=disabled\" > curl-abap-srv-output.txt 2>&1", + "after": "curl -L -vs -i -H \"X-CSRF-Token: Fetch\" \"https://.dest/sap/opu/odata/UI5/ABAP_REPOSITORY_SRV/Repositories(%27%27)?saml2=disabled\" > curl-abap-srv-output.txt 2>&1", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "curl 'https://mydestination_ui5.dest/resources/sap-ui-core.js' -X GET -i -H 'X-Csrf-Token: fetch' > output-tsk1.txt", + "after": "curl -L 'https://mydestination_ui5.dest/resources/sap-ui-core.js' -X GET -i -H 'X-Csrf-Token: fetch' > output-tsk1.txt", + "type": "content", + "commit": "eac8735", + "file": "misc/destinations/README.md" + }, + { + "before": "Example connection test (BAS or any terminal):", + "after": "Example connection test (BAS or any terminal window):", + "type": "content", + "commit": "e886eeb", + "file": "misc/onpremise/README.md" + }, + { + "before": "Step 3: Once you've gathered the logs, you can disable the trace settings.", + "after": "Step 3: Once you've gathered the following logs, you can disable the trace settings.", + "type": "content", + "commit": "1be0710", + "file": "misc/onpremise/README.md" + }, + { + "before": "Step3. Access your Security XSUAA credentials", + "after": "Step 3. Access your Security XSUAA credentials", + "type": "content", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "Step 2. Next, run the scenario that is failing and check the logs for any errors or else run Environment Check to call the V2 and V4 catalog API endpoints. The logs will provide detailed information about the requests and responses between the SAP BTP and the on-premise system.", + "after": "Step 2. Run the scenario that is failing and check the logs for any errors. If there are no errors, run the Environment Check to call the V2 and V4 catalog API endpoints. The logs will provide detailed information about the requests and responses between the SAP BTP and the On-Premise system.", + "type": "content", + "commit": "f9c170f", + "file": "cap/destination/README.md" + } + ], + "structure": [], + "technical": [ + { + "before": "- Are the authentication settings in the destination and back-end system aligned (such as principal propagation, SSL and xerts)?", + "after": "- Are the authentication settings in the destination and back-end system aligned (such as principal propagation, SSL and certs)?", + "type": "typo", + "commit": "a125c1a", + "file": "misc/onpremise/README.md" + }, + { + "before": "4. Output from ABAP transaction logs `/IWFND/ERROR_LOG` and `/IWFND/GW_CLIENT`. For more information, see [SAP ABAP guide](https://www.youtube.com/watch?v=Tmb-O966GwM).", + "after": "- Output from ABAP transaction logs `/IWFND/ERROR_LOG` and `/IWFND/GW_CLIENT`. For more information, see [SAP ABAP guide](https://www.youtube.com/watch?v=Tmb-O966GwM).", + "type": "typo", + "commit": "a125c1a", + "file": "misc/onpremise/README.md" + }, + { + "before": "In most on-premise configurations, principal propagation is the recommended implementation to support end-user identification. Principal propagation is an authentication mechanism used primarily in SAP Cloud and hybrid system landscapes to securely forward (or propagate) a user’s identity from one system or layer to another without re-authenticating the user at each hop.", + "after": "In most on-premise configurations, principal propagation is the recommended implementation to support end-user identification. Principal propagation is an authentication mechanism used primarily in SAP Cloud and hybrid system landscapes to securely forward (or propagate) a user's identity from one system or layer to another without re-authenticating the user at each hop.", + "type": "typo", + "commit": "a125c1a", + "file": "misc/onpremise/README.md" + }, + { + "before": "If a user logs into a SAP Fiori app on SAP BTP, and that app calls an on-premise SAP S/4HANA system, principal propagation allows the user’s identity to be sent end-to-end, so SAP S/4HANA knows exactly which user made the request, rather than seeing a generic `technical` user.", + "after": "If a user logs into a SAP Fiori app on SAP BTP, and that app calls an on-premise SAP S/4HANA system, principal propagation allows the user's identity to be sent end-to-end, so SAP S/4HANA knows exactly which user made the request, rather than seeing a generic `technical` user.", + "type": "typo", + "commit": "a125c1a", + "file": "misc/onpremise/README.md" + }, + { + "before": "Step 6: Create a new destination in your SAP BTP account, navigate to the `Connectivity` service and select `Destinations` and `Create destination` and change the `Authentication` type to `OAuth2ClientCredentials`;", + "after": "Step 6: Create a new destination in your SAP BTP account, navigate to the `Connectivity` service and select `Destinations` and `Create destination` and change the `Authentication` type to `OAuth2ClientCredentials`.", + "type": "typo", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "If you are still experiencing issues, please raise a support ticket using the support component `BC-MID-SCC` and ensure you provide the following information;", + "after": "If you are still experiencing issues, please raise a support ticket using the support component `BC-MID-SCC` and ensure you provide the following information:", + "type": "typo", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "### Option 1. ABAP Transaction Log", + "after": "### Option 1: ABAP Transaction Log", + "type": "typo", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "### Option 2. Enable Trace Logging", + "after": "### Option 2: Enable Trace Logging", + "type": "typo", + "commit": "c9aa85d", + "file": "cap/cap-fiori-hybrid/changes.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "Copyright (c) 2009-2025 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../LICENSES/Apache-2.0.txt) file.", + "after": "Copyright (c) 2009-2026 SAP SE or an SAP affiliate company. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](../LICENSES/Apache-2.0.txt) file.", + "type": "typo", + "commit": "0313136", + "file": "cap/README.md" + }, + { + "before": "1. Accessing On-Premises SAP systems such as SAP S/4HANA and ECC.", + "after": "1. Accessing On-Premises SAP systems such as SAP S/4HANA and ECC", + "type": "typo", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "- You have a SAP BTP, Cloud Foundry runtime environment configured in your SAP BTP subaccount.", + "after": "- You have a SAP BTP Cloud Foundry runtime environment configured in your SAP BTP subaccount.", + "type": "typo", + "commit": "ba36309", + "file": "misc/onpremise/README.md" + }, + { + "before": "## Step 1: Provide Screenshots", + "after": "## Step 1: Required Screenshots", + "type": "typo", + "commit": "1be0710", + "file": "misc/onpremise/README.md" + }, + { + "before": "````", + "after": "```", + "type": "typo", + "commit": "d21d1e8", + "file": "misc/onpremise/README.md" + }, + { + "before": "SAP BTP destinations are used to connect to different services and systems in the cloud, onpremise or any publicly available endpoints. They are used to define the connection parameters for the service you want to consume. The destination is a logical representation of the service and contains all the information required to connect to it.", + "after": "SAP BTP destinations are used to connect to different services and systems in the cloud, On-Premise or any publicly available endpoints. They are used to define the connection parameters for the service you want to consume. The destination is a logical representation of the service and contains all the information required to connect to it.", + "type": "typo", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "# SAP Cloud Connector (OnPremise) Destination", + "after": "# SAP Cloud Connector (On-Premise) Destination", + "type": "typo", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "- `URL` is set to `http://my-internal-host:44330/` which indicates the internal URL that is then mapped to your on-premise system within your local onpremise network. Note, the URL will always default to `http://` soo only the port and address are configurable.", + "after": "- `URL` is set to `http://my-internal-host:44330/` which indicates the internal URL that is then mapped to your on-premise system within your local On-Premise network. Note, the URL will always default to `http://` soo only the port and address are configurable.", + "type": "typo", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "Step 1. Enable logging in the SAP Cloud Connector (SCC) UI", + "after": "Step 1. Enable logging in the SAP Cloud Connector (SCC) UI.", + "type": "typo", + "commit": "f9c170f", + "file": "cap/destination/README.md" + }, + { + "before": "- Understanding [SAP BTP destinations](https://learning.sap.com/learning-journeys/administrating-sap-business-technology-platform/using-destinations)", + "after": "- Understanding [SAP BTP destinations](https://learning.sap.com/courses/operating-sap-business-technology-platform/using-destinations)", + "type": "typo", + "commit": "85189f6", + "file": "misc/destinations/README.md" + } + ] +} \ No newline at end of file diff --git a/training-data/quality-examples.json b/training-data/quality-examples.json new file mode 100644 index 00000000..6c3fac58 --- /dev/null +++ b/training-data/quality-examples.json @@ -0,0 +1,37 @@ +[ + { + "file": "./misc/headless/fioriui/README.md", + "totalCommits": 5, + "recentCommits": 1, + "size": 36941, + "score": 55.941 + }, + { + "file": "./sample-fiori-gen-ext/README.md", + "totalCommits": 16, + "recentCommits": 0, + "size": 13389, + "score": 55.388999999999996 + }, + { + "file": "./misc/cicd/README.md", + "totalCommits": 8, + "recentCommits": 0, + "size": 6227, + "score": 32.227000000000004 + }, + { + "file": "./cap/cap-fiori-hybrid/README.md", + "totalCommits": 7, + "recentCommits": 2, + "size": 5145, + "score": 27.145 + }, + { + "file": "./misc/proxy/README.md", + "totalCommits": 6, + "recentCommits": 2, + "size": 6533, + "score": 26.533 + } +] \ No newline at end of file