@thanhvn14/csvibe 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/agents/schemas/base-output.schema.json +88 -0
- package/.github/agents/schemas/brainstorm-output.schema.json +88 -0
- package/.github/agents/schemas/scout-output.schema.json +60 -0
- package/.github/agents/scripts/fetch-copilot-tools.js +245 -0
- package/.github/agents/scripts/lib/parse-agent-file.js +275 -0
- package/.github/agents/scripts/package-lock.json +78 -0
- package/.github/agents/scripts/package.json +22 -0
- package/.github/agents/scripts/schemas/agent-frontmatter.schema.json +83 -0
- package/.github/agents/scripts/validate-agent-all.js +157 -0
- package/.github/agents/scripts/validate-agent-frontmatter.js +96 -0
- package/.github/agents/scripts/validate-agent-handoffs.js +169 -0
- package/.github/agents/scripts/validate-agent-output.js +157 -0
- package/.github/agents/scripts/validate-agent-tools.js +278 -0
- package/.github/skills/.env.example +100 -0
- package/.github/skills/.install-state.json +23 -0
- package/.github/skills/README.md +149 -0
- package/.github/skills/ai-multimodal/.env.example +204 -0
- package/.github/skills/ai-multimodal/scripts/.coverage +0 -0
- package/.github/skills/ai-multimodal/scripts/check_setup.py +305 -0
- package/.github/skills/ai-multimodal/scripts/document_converter.py +395 -0
- package/.github/skills/ai-multimodal/scripts/gemini_batch_process.py +1184 -0
- package/.github/skills/ai-multimodal/scripts/media_optimizer.py +506 -0
- package/.github/skills/ai-multimodal/scripts/requirements.txt +26 -0
- package/.github/skills/better-auth/scripts/.coverage +0 -0
- package/.github/skills/better-auth/scripts/better_auth_init.py +521 -0
- package/.github/skills/better-auth/scripts/requirements.txt +15 -0
- package/.github/skills/chrome-devtools/scripts/README.md +272 -0
- package/.github/skills/chrome-devtools/scripts/__tests__/selector.test.js +210 -0
- package/.github/skills/chrome-devtools/scripts/aria-snapshot.js +362 -0
- package/.github/skills/chrome-devtools/scripts/click.js +83 -0
- package/.github/skills/chrome-devtools/scripts/console.js +79 -0
- package/.github/skills/chrome-devtools/scripts/evaluate.js +53 -0
- package/.github/skills/chrome-devtools/scripts/fill.js +76 -0
- package/.github/skills/chrome-devtools/scripts/inject-auth.js +229 -0
- package/.github/skills/chrome-devtools/scripts/install-deps.sh +181 -0
- package/.github/skills/chrome-devtools/scripts/install.sh +83 -0
- package/.github/skills/chrome-devtools/scripts/lib/browser.js +318 -0
- package/.github/skills/chrome-devtools/scripts/lib/selector.js +178 -0
- package/.github/skills/chrome-devtools/scripts/navigate.js +54 -0
- package/.github/skills/chrome-devtools/scripts/network.js +106 -0
- package/.github/skills/chrome-devtools/scripts/package-lock.json +1589 -0
- package/.github/skills/chrome-devtools/scripts/package.json +16 -0
- package/.github/skills/chrome-devtools/scripts/performance.js +149 -0
- package/.github/skills/chrome-devtools/scripts/screenshot.js +198 -0
- package/.github/skills/chrome-devtools/scripts/select-ref.js +131 -0
- package/.github/skills/chrome-devtools/scripts/snapshot.js +135 -0
- package/.github/skills/common/README.md +120 -0
- package/.github/skills/common/api_key_helper.py +411 -0
- package/.github/skills/common/api_key_rotator.py +248 -0
- package/.github/skills/databases/scripts/.coverage +0 -0
- package/.github/skills/databases/scripts/db_backup.py +502 -0
- package/.github/skills/databases/scripts/db_migrate.py +425 -0
- package/.github/skills/databases/scripts/db_performance_check.py +456 -0
- package/.github/skills/databases/scripts/requirements.txt +20 -0
- package/.github/skills/debugging/scripts/find-polluter.sh +63 -0
- package/.github/skills/devops/.env.example +76 -0
- package/.github/skills/devops/scripts/cloudflare_deploy.py +269 -0
- package/.github/skills/devops/scripts/docker_optimize.py +331 -0
- package/.github/skills/devops/scripts/requirements.txt +20 -0
- package/.github/skills/docs-seeker/.env.example +15 -0
- package/.github/skills/docs-seeker/package.json +25 -0
- package/.github/skills/docs-seeker/scripts/analyze-llms-txt.js +211 -0
- package/.github/skills/docs-seeker/scripts/detect-topic.js +172 -0
- package/.github/skills/docs-seeker/scripts/fetch-docs.js +213 -0
- package/.github/skills/docs-seeker/scripts/utils/env-loader.js +94 -0
- package/.github/skills/document-skills/docx/LICENSE.txt +30 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/pack.py +159 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/unpack.py +29 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validate.py +69 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/base.py +951 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/docx.py +274 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/.github/skills/document-skills/docx/scripts/__init__.py +1 -0
- package/.github/skills/document-skills/docx/scripts/document.py +1276 -0
- package/.github/skills/document-skills/docx/scripts/templates/comments.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/people.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/utilities.py +374 -0
- package/.github/skills/document-skills/pdf/LICENSE.txt +30 -0
- package/.github/skills/document-skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/.github/skills/document-skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/.github/skills/document-skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/.github/skills/document-skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/.github/skills/document-skills/pdf/scripts/create_validation_image.py +41 -0
- package/.github/skills/document-skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/.github/skills/document-skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/.github/skills/document-skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/.github/skills/document-skills/pptx/LICENSE.txt +30 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/pack.py +159 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/unpack.py +29 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validate.py +69 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/base.py +951 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/.github/skills/document-skills/pptx/scripts/html2pptx.js +979 -0
- package/.github/skills/document-skills/pptx/scripts/inventory.py +1020 -0
- package/.github/skills/document-skills/pptx/scripts/rearrange.py +231 -0
- package/.github/skills/document-skills/pptx/scripts/replace.py +385 -0
- package/.github/skills/document-skills/pptx/scripts/thumbnail.py +450 -0
- package/.github/skills/document-skills/xlsx/LICENSE.txt +30 -0
- package/.github/skills/document-skills/xlsx/recalc.py +190 -0
- package/.github/skills/install.ps1 +1220 -0
- package/.github/skills/install.sh +1032 -0
- package/.github/skills/markdown-novel-viewer/assets/directory-browser.css +215 -0
- package/.github/skills/markdown-novel-viewer/assets/favicon.png +0 -0
- package/.github/skills/markdown-novel-viewer/assets/novel-theme.css +818 -0
- package/.github/skills/markdown-novel-viewer/assets/reader.js +262 -0
- package/.github/skills/markdown-novel-viewer/assets/template.html +80 -0
- package/.github/skills/markdown-novel-viewer/package-lock.json +146 -0
- package/.github/skills/markdown-novel-viewer/package.json +15 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/http-server.cjs +434 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/markdown-renderer.cjs +272 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/plan-navigator.cjs +509 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/port-finder.cjs +48 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/process-mgr.cjs +150 -0
- package/.github/skills/markdown-novel-viewer/scripts/server.cjs +411 -0
- package/.github/skills/mcp-builder/LICENSE.txt +202 -0
- package/.github/skills/mcp-builder/scripts/connections.py +151 -0
- package/.github/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/.github/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/.github/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/.github/skills/mcp-management/README.md +219 -0
- package/.github/skills/mcp-management/assets/tools.json +3146 -0
- package/.github/skills/mcp-management/package-lock.json +6 -0
- package/.github/skills/mcp-management/scripts/.env.example +10 -0
- package/.github/skills/mcp-management/scripts/cli.ts +195 -0
- package/.github/skills/mcp-management/scripts/dist/analyze-tools.js +70 -0
- package/.github/skills/mcp-management/scripts/dist/cli.js +160 -0
- package/.github/skills/mcp-management/scripts/dist/mcp-client.js +183 -0
- package/.github/skills/mcp-management/scripts/mcp-client.ts +230 -0
- package/.github/skills/mcp-management/scripts/package.json +20 -0
- package/.github/skills/media-processing/scripts/README.md +111 -0
- package/.github/skills/media-processing/scripts/batch-remove-background.sh +124 -0
- package/.github/skills/media-processing/scripts/batch_resize.py +342 -0
- package/.github/skills/media-processing/scripts/media_convert.py +311 -0
- package/.github/skills/media-processing/scripts/remove-background.sh +96 -0
- package/.github/skills/media-processing/scripts/remove-bg-node.js +158 -0
- package/.github/skills/media-processing/scripts/requirements.txt +24 -0
- package/.github/skills/media-processing/scripts/video_optimize.py +414 -0
- package/.github/skills/payment-integration/README.md +185 -0
- package/.github/skills/payment-integration/scripts/.env.example +20 -0
- package/.github/skills/payment-integration/scripts/checkout-helper.js +244 -0
- package/.github/skills/payment-integration/scripts/package.json +17 -0
- package/.github/skills/payment-integration/scripts/polar-webhook-verify.js +202 -0
- package/.github/skills/payment-integration/scripts/sepay-webhook-verify.js +193 -0
- package/.github/skills/payment-integration/scripts/test-scripts.js +237 -0
- package/.github/skills/plans-kanban/assets/dashboard-template.html +119 -0
- package/.github/skills/plans-kanban/assets/dashboard.css +1594 -0
- package/.github/skills/plans-kanban/assets/dashboard.js +596 -0
- package/.github/skills/plans-kanban/assets/favicon.png +0 -0
- package/.github/skills/plans-kanban/package-lock.json +123 -0
- package/.github/skills/plans-kanban/package.json +13 -0
- package/.github/skills/plans-kanban/scripts/lib/dashboard-renderer.cjs +884 -0
- package/.github/skills/plans-kanban/scripts/lib/http-server.cjs +310 -0
- package/.github/skills/plans-kanban/scripts/lib/plan-metadata-extractor.cjs +489 -0
- package/.github/skills/plans-kanban/scripts/lib/plan-parser.cjs +175 -0
- package/.github/skills/plans-kanban/scripts/lib/plan-scanner.cjs +272 -0
- package/.github/skills/plans-kanban/scripts/lib/port-finder.cjs +48 -0
- package/.github/skills/plans-kanban/scripts/lib/process-mgr.cjs +128 -0
- package/.github/skills/plans-kanban/scripts/server.cjs +260 -0
- package/.github/skills/repomix/scripts/.coverage +0 -0
- package/.github/skills/repomix/scripts/README.md +179 -0
- package/.github/skills/repomix/scripts/repomix_batch.py +455 -0
- package/.github/skills/repomix/scripts/repos.example.json +15 -0
- package/.github/skills/repomix/scripts/requirements.txt +15 -0
- package/.github/skills/scout-validation/scripts/lib/broad-pattern-detector.cjs +124 -0
- package/.github/skills/scout-validation/scripts/lib/path-checker.cjs +66 -0
- package/.github/skills/scout-validation/scripts/lib/schema-validator.cjs +45 -0
- package/.github/skills/scout-validation/scripts/package.json +11 -0
- package/.github/skills/scout-validation/scripts/validate-scout-output.cjs +219 -0
- package/.github/skills/scout-validation/test/broad-pattern-output.json +18 -0
- package/.github/skills/scout-validation/test/invalid-path-output.json +18 -0
- package/.github/skills/scout-validation/test/valid-scout-output.json +26 -0
- package/.github/skills/sequential-thinking/.env.example +8 -0
- package/.github/skills/sequential-thinking/README.md +183 -0
- package/.github/skills/sequential-thinking/package.json +31 -0
- package/.github/skills/sequential-thinking/scripts/format-thought.js +159 -0
- package/.github/skills/sequential-thinking/scripts/process-thought.js +236 -0
- package/.github/skills/shopify/README.md +66 -0
- package/.github/skills/shopify/scripts/.coverage +0 -0
- package/.github/skills/shopify/scripts/requirements.txt +19 -0
- package/.github/skills/shopify/scripts/shopify_init.py +423 -0
- package/.github/skills/skill-creator/LICENSE.txt +202 -0
- package/.github/skills/skill-creator/scripts/init_skill.py +303 -0
- package/.github/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.github/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/.github/skills/ui-styling/LICENSE.txt +202 -0
- package/.github/skills/ui-styling/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/DMMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/.github/skills/ui-styling/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Gloock-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Italiana-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Jura-Light.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Jura-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Jura-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Outfit-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Tektur-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/.github/skills/ui-styling/scripts/.coverage +0 -0
- package/.github/skills/ui-styling/scripts/requirements.txt +17 -0
- package/.github/skills/ui-styling/scripts/shadcn_add.py +292 -0
- package/.github/skills/ui-styling/scripts/tailwind_config_gen.py +456 -0
- package/.github/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.github/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.github/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.github/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.github/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +51 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.github/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.github/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.github/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.github/skills/ui-ux-pro-max/scripts/core.py +236 -0
- package/.github/skills/ui-ux-pro-max/scripts/search.py +76 -0
- package/.github/skills/web-frameworks/scripts/.coverage +0 -0
- package/.github/skills/web-frameworks/scripts/__init__.py +0 -0
- package/.github/skills/web-frameworks/scripts/nextjs_init.py +547 -0
- package/.github/skills/web-frameworks/scripts/requirements.txt +16 -0
- package/.github/skills/web-frameworks/scripts/turborepo_migrate.py +394 -0
- package/dist/config/constants.d.ts +3 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +5 -1
- package/dist/config/constants.js.map +1 -1
- package/dist/domains/github/github-client.d.ts +5 -0
- package/dist/domains/github/github-client.d.ts.map +1 -1
- package/dist/domains/github/github-client.js +44 -0
- package/dist/domains/github/github-client.js.map +1 -1
- package/dist/utils/downloader.d.ts +3 -1
- package/dist/utils/downloader.d.ts.map +1 -1
- package/dist/utils/downloader.js +48 -11
- package/dist/utils/downloader.js.map +1 -1
- package/dist/utils/scaffolder.d.ts.map +1 -1
- package/dist/utils/scaffolder.js +2 -0
- package/dist/utils/scaffolder.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan navigation system - detects plan structure and generates navigation
|
|
3
|
+
* Enables sidebar navigation for multi-phase plans
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detect if a file is part of a plan directory
|
|
11
|
+
* @param {string} filePath - Path to markdown file
|
|
12
|
+
* @returns {{isPlan: boolean, planDir: string, planFile: string, phases: Array}}
|
|
13
|
+
*/
|
|
14
|
+
function detectPlan(filePath) {
|
|
15
|
+
const dir = path.dirname(filePath);
|
|
16
|
+
const planFile = path.join(dir, 'plan.md');
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(planFile)) {
|
|
19
|
+
return { isPlan: false };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Find all phase files
|
|
23
|
+
const files = fs.readdirSync(dir);
|
|
24
|
+
const phases = files
|
|
25
|
+
.filter(f => f.startsWith('phase-') && f.endsWith('.md'))
|
|
26
|
+
.sort((a, b) => {
|
|
27
|
+
// Sort by phase number
|
|
28
|
+
const numA = parseInt(a.match(/phase-(\d+)/)?.[1] || '0', 10);
|
|
29
|
+
const numB = parseInt(b.match(/phase-(\d+)/)?.[1] || '0', 10);
|
|
30
|
+
return numA - numB;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
isPlan: true,
|
|
35
|
+
planDir: dir,
|
|
36
|
+
planFile,
|
|
37
|
+
phases: phases.map(f => path.join(dir, f))
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Normalize status string to standard format
|
|
43
|
+
* @param {string} raw - Raw status text
|
|
44
|
+
* @returns {string} - Normalized status (completed, in-progress, pending)
|
|
45
|
+
*/
|
|
46
|
+
function normalizeStatus(raw) {
|
|
47
|
+
const s = (raw || '').toLowerCase().trim();
|
|
48
|
+
// Match various completed indicators
|
|
49
|
+
if (s.includes('complete') || s.includes('done') || s.includes('✓') || s.includes('✅')) {
|
|
50
|
+
return 'completed';
|
|
51
|
+
}
|
|
52
|
+
// Match in-progress indicators
|
|
53
|
+
if (s.includes('progress') || s.includes('active') || s.includes('wip') || s.includes('🔄')) {
|
|
54
|
+
return 'in-progress';
|
|
55
|
+
}
|
|
56
|
+
return 'pending';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate a slug from text for use as anchor ID
|
|
61
|
+
* @param {string} text - Text to slugify
|
|
62
|
+
* @returns {string} - URL-safe slug
|
|
63
|
+
*/
|
|
64
|
+
function slugify(text) {
|
|
65
|
+
return text
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
68
|
+
.replace(/^-|-$/g, '');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse plan.md to extract phase metadata from table
|
|
73
|
+
* Supports multiple table formats:
|
|
74
|
+
* 1. Standard: | Phase | Name | Status | [Link](path) |
|
|
75
|
+
* 2. Link-first: | [Phase X](path) | Description | Status | ... |
|
|
76
|
+
* 3. Heading-based: ### Phase X: Name with - Status: XXX
|
|
77
|
+
* @param {string} planFilePath - Path to plan.md
|
|
78
|
+
* @returns {Array<{phase: number, name: string, status: string, file: string, anchor: string}>}
|
|
79
|
+
*/
|
|
80
|
+
function parsePlanTable(planFilePath) {
|
|
81
|
+
const content = fs.readFileSync(planFilePath, 'utf8');
|
|
82
|
+
const dir = path.dirname(planFilePath);
|
|
83
|
+
const phases = [];
|
|
84
|
+
|
|
85
|
+
// Format 1: Standard table | Phase | Name | Status | [Link](path) |
|
|
86
|
+
const standardRegex = /\|\s*(\d+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|\s*\[([^\]]+)\]\(([^)]+)\)/g;
|
|
87
|
+
let match;
|
|
88
|
+
while ((match = standardRegex.exec(content)) !== null) {
|
|
89
|
+
const [, phase, name, status, linkText, linkPath] = match;
|
|
90
|
+
phases.push({
|
|
91
|
+
phase: parseInt(phase, 10),
|
|
92
|
+
name: name.trim(),
|
|
93
|
+
status: normalizeStatus(status),
|
|
94
|
+
file: path.resolve(dir, linkPath),
|
|
95
|
+
linkText: linkText.trim()
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Format 2: Link-first table | [Phase X](path) | Description | Status | ... |
|
|
100
|
+
// Matches: | [Phase 1](phase-01-xxx.md) | Description | ✓ Complete | 4h |
|
|
101
|
+
if (phases.length === 0) {
|
|
102
|
+
const linkFirstRegex = /\|\s*\[(?:Phase\s*)?(\d+)\]\(([^)]+)\)\s*\|\s*([^|]+)\s*\|\s*([^|]+)/g;
|
|
103
|
+
while ((match = linkFirstRegex.exec(content)) !== null) {
|
|
104
|
+
const [, phase, linkPath, name, status] = match;
|
|
105
|
+
phases.push({
|
|
106
|
+
phase: parseInt(phase, 10),
|
|
107
|
+
name: name.trim(),
|
|
108
|
+
status: normalizeStatus(status),
|
|
109
|
+
file: path.resolve(dir, linkPath),
|
|
110
|
+
linkText: `Phase ${phase}`
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Format 2b: Number-first with link in col 2: | 1 | [Name](path) | Status | ... |
|
|
116
|
+
// Matches: | 1 | [Tab Structure](./phase-01-xxx.md) | Pending | High | 4h |
|
|
117
|
+
if (phases.length === 0) {
|
|
118
|
+
const numLinkRegex = /\|\s*(\d+)\s*\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*([^|]+)/g;
|
|
119
|
+
while ((match = numLinkRegex.exec(content)) !== null) {
|
|
120
|
+
const [, phase, name, linkPath, status] = match;
|
|
121
|
+
phases.push({
|
|
122
|
+
phase: parseInt(phase, 10),
|
|
123
|
+
name: name.trim(),
|
|
124
|
+
status: normalizeStatus(status),
|
|
125
|
+
file: path.resolve(dir, linkPath),
|
|
126
|
+
linkText: name.trim()
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Format 2c: Simple table without links: | Phase | Description | Status |
|
|
132
|
+
// Matches: | 01 | Backend: Install deps | Completed ✅ |
|
|
133
|
+
if (phases.length === 0) {
|
|
134
|
+
const simpleTblRegex = /\|\s*0?(\d+)\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|/g;
|
|
135
|
+
while ((match = simpleTblRegex.exec(content)) !== null) {
|
|
136
|
+
const [fullMatch, phase, name, status] = match;
|
|
137
|
+
// Skip header rows and separator rows
|
|
138
|
+
if (name.trim().toLowerCase() === 'description' || name.trim().toLowerCase() === 'name') continue;
|
|
139
|
+
if (name.includes('---') || name.includes('===')) continue;
|
|
140
|
+
const phaseNum = parseInt(phase, 10);
|
|
141
|
+
phases.push({
|
|
142
|
+
phase: phaseNum,
|
|
143
|
+
name: name.trim(),
|
|
144
|
+
status: normalizeStatus(status),
|
|
145
|
+
file: planFilePath,
|
|
146
|
+
linkText: name.trim(),
|
|
147
|
+
anchor: `phase-${String(phaseNum).padStart(2, '0')}-${slugify(name.trim())}`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Format 3: Heading-based phases (### Phase X: Name with - Status: XXX)
|
|
153
|
+
if (phases.length === 0) {
|
|
154
|
+
const contentLines = content.split('\n');
|
|
155
|
+
let currentPhase = null;
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < contentLines.length; i++) {
|
|
158
|
+
const line = contentLines[i];
|
|
159
|
+
const headingMatch = /###\s*Phase\s*(\d+)[:\s]+(.+)/i.exec(line);
|
|
160
|
+
if (headingMatch) {
|
|
161
|
+
if (currentPhase) phases.push(currentPhase);
|
|
162
|
+
const phaseNum = parseInt(headingMatch[1], 10);
|
|
163
|
+
const phaseName = headingMatch[2].trim();
|
|
164
|
+
currentPhase = {
|
|
165
|
+
phase: phaseNum,
|
|
166
|
+
name: phaseName,
|
|
167
|
+
status: 'pending',
|
|
168
|
+
file: planFilePath,
|
|
169
|
+
linkText: `Phase ${phaseNum}`,
|
|
170
|
+
anchor: `phase-${String(phaseNum).padStart(2, '0')}-${slugify(phaseName)}`
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Look for status in subsequent lines
|
|
174
|
+
if (currentPhase) {
|
|
175
|
+
const statusMatch = /-\s*Status:\s*(.+)/i.exec(line);
|
|
176
|
+
if (statusMatch) {
|
|
177
|
+
currentPhase.status = normalizeStatus(statusMatch[1]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (currentPhase) phases.push(currentPhase);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Format 4: Bullet-list phases with nested File: references (check early - specific pattern)
|
|
185
|
+
// Matches:
|
|
186
|
+
// - Phase 01: Name ✅ (date)
|
|
187
|
+
// - File: `phase-01-name.md`
|
|
188
|
+
// - Completed: date
|
|
189
|
+
// Check if content has this specific pattern before proceeding
|
|
190
|
+
if (phases.length === 0 && /^-\s*Phase\s*\d+[:\s]/m.test(content)) {
|
|
191
|
+
const lines = content.split('\n');
|
|
192
|
+
let currentPhase = null;
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i < lines.length; i++) {
|
|
195
|
+
const line = lines[i];
|
|
196
|
+
|
|
197
|
+
// Match phase line: "- Phase 01: Name ✅" or "- Phase 01: Name (date)"
|
|
198
|
+
const phaseMatch = /^-\s*Phase\s*0?(\d+)[:\s]+([^✅✓\n]+)/i.exec(line);
|
|
199
|
+
if (phaseMatch) {
|
|
200
|
+
// Save previous phase if exists
|
|
201
|
+
if (currentPhase) phases.push(currentPhase);
|
|
202
|
+
|
|
203
|
+
const phaseNum = parseInt(phaseMatch[1], 10);
|
|
204
|
+
const name = phaseMatch[2].trim().replace(/\s*\([^)]*\)\s*$/, ''); // Remove trailing (date)
|
|
205
|
+
const hasCheckmark = /[✅✓]/.test(line);
|
|
206
|
+
|
|
207
|
+
currentPhase = {
|
|
208
|
+
phase: phaseNum,
|
|
209
|
+
name: name,
|
|
210
|
+
status: hasCheckmark ? 'completed' : 'pending',
|
|
211
|
+
file: planFilePath, // Default to plan.md, will be updated if File: found
|
|
212
|
+
linkText: name,
|
|
213
|
+
anchor: `phase-${String(phaseNum).padStart(2, '0')}-${slugify(name)}`
|
|
214
|
+
};
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Look for nested File: reference within current phase
|
|
219
|
+
if (currentPhase) {
|
|
220
|
+
const fileMatch = /^\s+-\s*File:\s*`?([^`\n]+)`?/i.exec(line);
|
|
221
|
+
if (fileMatch) {
|
|
222
|
+
const fileName = fileMatch[1].trim();
|
|
223
|
+
currentPhase.file = path.resolve(dir, fileName);
|
|
224
|
+
// Clear anchor when separate file exists
|
|
225
|
+
currentPhase.anchor = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check for status indicators in nested lines
|
|
229
|
+
const statusMatch = /^\s+-\s*(Completed|Status):\s*(.+)/i.exec(line);
|
|
230
|
+
if (statusMatch) {
|
|
231
|
+
currentPhase.status = normalizeStatus(statusMatch[2]);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// End current phase when we hit another top-level non-phase item or section header
|
|
235
|
+
if (/^##/.test(line) || (/^-\s/.test(line) && !/^-\s*Phase/i.test(line) && !/^\s+-/.test(line))) {
|
|
236
|
+
phases.push(currentPhase);
|
|
237
|
+
currentPhase = null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Push last phase if exists
|
|
243
|
+
if (currentPhase) phases.push(currentPhase);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Format 5: Numbered list phases with checkbox status
|
|
247
|
+
// Matches: 1) **Discovery** with status from - [x] Discovery: ...
|
|
248
|
+
if (phases.length === 0) {
|
|
249
|
+
// First pass: find numbered phases like "1) **Name**" or "1. **Name**"
|
|
250
|
+
const numberedPhaseRegex = /^(\d+)[)\.]\s*\*\*([^*]+)\*\*/gm;
|
|
251
|
+
const phaseMap = new Map();
|
|
252
|
+
while ((match = numberedPhaseRegex.exec(content)) !== null) {
|
|
253
|
+
const [, num, name] = match;
|
|
254
|
+
const phaseNum = parseInt(num, 10);
|
|
255
|
+
phaseMap.set(name.trim().toLowerCase(), {
|
|
256
|
+
phase: phaseNum,
|
|
257
|
+
name: name.trim(),
|
|
258
|
+
status: 'pending',
|
|
259
|
+
file: planFilePath,
|
|
260
|
+
linkText: name.trim(),
|
|
261
|
+
anchor: `phase-${String(phaseNum).padStart(2, '0')}-${slugify(name.trim())}`
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Second pass: find checkbox status like "- [x] Name:" or "- [ ] Name:"
|
|
266
|
+
const checkboxRegex = /^-\s*\[(x| )\]\s*([^:]+)/gmi;
|
|
267
|
+
while ((match = checkboxRegex.exec(content)) !== null) {
|
|
268
|
+
const [, checked, name] = match;
|
|
269
|
+
const key = name.trim().toLowerCase();
|
|
270
|
+
if (phaseMap.has(key)) {
|
|
271
|
+
phaseMap.get(key).status = checked.toLowerCase() === 'x' ? 'completed' : 'pending';
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Convert map to array sorted by phase number
|
|
276
|
+
if (phaseMap.size > 0) {
|
|
277
|
+
phases.push(...Array.from(phaseMap.values()).sort((a, b) => a.phase - b.phase));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Format 6: Checkbox list with bold links
|
|
282
|
+
// Matches: - [ ] **[Phase 1: Name](./phase-01-xxx.md)** or - [x] **[Phase 1](path)**
|
|
283
|
+
if (phases.length === 0) {
|
|
284
|
+
const checkboxLinkRegex = /^-\s*\[(x| )\]\s*\*\*\[(?:Phase\s*)?(\d+)[:\s]*([^\]]*)\]\(([^)]+)\)\*\*/gmi;
|
|
285
|
+
while ((match = checkboxLinkRegex.exec(content)) !== null) {
|
|
286
|
+
const [, checked, phase, name, linkPath] = match;
|
|
287
|
+
phases.push({
|
|
288
|
+
phase: parseInt(phase, 10),
|
|
289
|
+
name: name.trim() || `Phase ${phase}`,
|
|
290
|
+
status: checked.toLowerCase() === 'x' ? 'completed' : 'pending',
|
|
291
|
+
file: path.resolve(dir, linkPath),
|
|
292
|
+
linkText: name.trim() || `Phase ${phase}`
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Enhancement: Extract file paths from "Phase Files" section if phases point to plan.md
|
|
298
|
+
// This handles plans with heading-based phases + separate file links section
|
|
299
|
+
if (phases.length > 0) {
|
|
300
|
+
const phaseFilesSection = content.match(/##\s*Phase\s*Files[\s\S]*?(?=##|$)/i);
|
|
301
|
+
if (phaseFilesSection) {
|
|
302
|
+
const linkRegex = /\d+\.\s*\[([^\]]+)\]\(([^)]+\.md)\)/g;
|
|
303
|
+
let linkMatch;
|
|
304
|
+
while ((linkMatch = linkRegex.exec(phaseFilesSection[0])) !== null) {
|
|
305
|
+
const [, linkName, linkPath] = linkMatch;
|
|
306
|
+
// Extract phase number from filename (phase-01-xxx.md -> 1)
|
|
307
|
+
const phaseNum = parseInt(linkName.match(/phase-0?(\d+)/i)?.[1] || '0', 10);
|
|
308
|
+
// Update corresponding phase's file path
|
|
309
|
+
const phase = phases.find(p => p.phase === phaseNum);
|
|
310
|
+
if (phase && phase.file === planFilePath) {
|
|
311
|
+
phase.file = path.resolve(dir, linkPath);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Filter out phases that only point to the plan.md itself (inline sections)
|
|
318
|
+
// Only keep phases that have separate phase files
|
|
319
|
+
return phases.filter(p => p.file !== planFilePath);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get navigation context for a file
|
|
324
|
+
* @param {string} filePath - Current file path
|
|
325
|
+
* @returns {{planInfo: Object, currentIndex: number, prev: Object, next: Object, allPhases: Array}}
|
|
326
|
+
*/
|
|
327
|
+
function getNavigationContext(filePath) {
|
|
328
|
+
const planInfo = detectPlan(filePath);
|
|
329
|
+
|
|
330
|
+
if (!planInfo.isPlan) {
|
|
331
|
+
return { planInfo, currentIndex: -1, prev: null, next: null, allPhases: [] };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Parse plan table for metadata
|
|
335
|
+
const phaseMeta = parsePlanTable(planInfo.planFile);
|
|
336
|
+
|
|
337
|
+
// Build all phases list including plan.md
|
|
338
|
+
const allPhases = [
|
|
339
|
+
{
|
|
340
|
+
phase: 0,
|
|
341
|
+
name: 'Plan Overview',
|
|
342
|
+
status: 'overview',
|
|
343
|
+
file: planInfo.planFile
|
|
344
|
+
},
|
|
345
|
+
...phaseMeta
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
// Find current file index
|
|
349
|
+
const normalizedPath = path.normalize(filePath);
|
|
350
|
+
const currentIndex = allPhases.findIndex(p => path.normalize(p.file) === normalizedPath);
|
|
351
|
+
|
|
352
|
+
// Get prev/next
|
|
353
|
+
const prev = currentIndex > 0 ? allPhases[currentIndex - 1] : null;
|
|
354
|
+
const next = currentIndex < allPhases.length - 1 && currentIndex >= 0
|
|
355
|
+
? allPhases[currentIndex + 1]
|
|
356
|
+
: null;
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
planInfo,
|
|
360
|
+
currentIndex,
|
|
361
|
+
prev,
|
|
362
|
+
next,
|
|
363
|
+
allPhases
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Generate navigation sidebar HTML
|
|
369
|
+
* @param {string} filePath - Current file path
|
|
370
|
+
* @returns {string} - HTML navigation sidebar
|
|
371
|
+
*/
|
|
372
|
+
function generateNavSidebar(filePath) {
|
|
373
|
+
const { planInfo, currentIndex, allPhases } = getNavigationContext(filePath);
|
|
374
|
+
|
|
375
|
+
if (!planInfo.isPlan) {
|
|
376
|
+
return '';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const planName = path.basename(planInfo.planDir);
|
|
380
|
+
const normalizedCurrentPath = path.normalize(filePath);
|
|
381
|
+
|
|
382
|
+
const items = allPhases.map((phase, index) => {
|
|
383
|
+
const isActive = index === currentIndex;
|
|
384
|
+
const statusClass = phase.status.replace(/\s+/g, '-');
|
|
385
|
+
const normalizedPhasePath = path.normalize(phase.file);
|
|
386
|
+
const isSameFile = normalizedPhasePath === normalizedCurrentPath;
|
|
387
|
+
|
|
388
|
+
// Check if phase file actually exists on disk
|
|
389
|
+
const fileExists = fs.existsSync(phase.file);
|
|
390
|
+
const unavailableClass = !fileExists ? 'unavailable' : '';
|
|
391
|
+
|
|
392
|
+
// If file doesn't exist, render as non-clickable span with tooltip
|
|
393
|
+
if (!fileExists) {
|
|
394
|
+
return `
|
|
395
|
+
<li class="phase-item ${unavailableClass}" data-status="${statusClass}" title="Phase planned but not yet implemented">
|
|
396
|
+
<span class="phase-link-disabled">
|
|
397
|
+
<span class="status-dot ${statusClass}"></span>
|
|
398
|
+
<span class="phase-name">${phase.name}</span>
|
|
399
|
+
<span class="unavailable-badge">Planned</span>
|
|
400
|
+
</span>
|
|
401
|
+
</li>
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Build href: use anchor for same-file phases, full URL for different files
|
|
406
|
+
let href;
|
|
407
|
+
let isInlineSection = false;
|
|
408
|
+
if (isSameFile && phase.anchor) {
|
|
409
|
+
// Same file with anchor - use hash fragment only for smooth scrolling
|
|
410
|
+
href = `#${phase.anchor}`;
|
|
411
|
+
isInlineSection = true;
|
|
412
|
+
} else if (phase.anchor) {
|
|
413
|
+
// Different file with anchor
|
|
414
|
+
href = `/view?file=${encodeURIComponent(phase.file)}#${phase.anchor}`;
|
|
415
|
+
} else {
|
|
416
|
+
// No anchor (separate phase file or plan overview)
|
|
417
|
+
href = `/view?file=${encodeURIComponent(phase.file)}`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Add data attributes for client-side section tracking
|
|
421
|
+
const dataAnchor = phase.anchor ? `data-anchor="${phase.anchor}"` : '';
|
|
422
|
+
const inlineSectionClass = isInlineSection ? 'inline-section' : '';
|
|
423
|
+
|
|
424
|
+
// Type icon: hash/anchor for inline sections, file for separate docs
|
|
425
|
+
const typeIcon = isInlineSection
|
|
426
|
+
? `<svg class="phase-type-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/></svg>`
|
|
427
|
+
: `<svg class="phase-type-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M3.75 1.5a.25.25 0 00-.25.25v12.5c0 .138.112.25.25.25h8.5a.25.25 0 00.25-.25V4.664a.25.25 0 00-.073-.177l-2.914-2.914a.25.25 0 00-.177-.073H3.75zM2 1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0112.25 16h-8.5A1.75 1.75 0 012 14.25V1.75z"/></svg>`;
|
|
428
|
+
|
|
429
|
+
return `
|
|
430
|
+
<li class="phase-item ${isActive ? 'active' : ''} ${inlineSectionClass}" data-status="${statusClass}" ${dataAnchor}>
|
|
431
|
+
<a href="${href}">
|
|
432
|
+
${typeIcon}
|
|
433
|
+
<span class="status-dot ${statusClass}"></span>
|
|
434
|
+
<span class="phase-name">${phase.name}</span>
|
|
435
|
+
</a>
|
|
436
|
+
</li>
|
|
437
|
+
`;
|
|
438
|
+
}).join('');
|
|
439
|
+
|
|
440
|
+
return `
|
|
441
|
+
<nav class="plan-nav" id="plan-nav">
|
|
442
|
+
<div class="plan-title">
|
|
443
|
+
<span class="plan-icon">📖</span>
|
|
444
|
+
<span>${planName}</span>
|
|
445
|
+
</div>
|
|
446
|
+
<ul class="phase-list">
|
|
447
|
+
${items}
|
|
448
|
+
</ul>
|
|
449
|
+
</nav>
|
|
450
|
+
`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Generate prev/next navigation footer
|
|
455
|
+
* @param {string} filePath - Current file path
|
|
456
|
+
* @returns {string} - HTML navigation footer
|
|
457
|
+
*/
|
|
458
|
+
function generateNavFooter(filePath) {
|
|
459
|
+
const { prev, next } = getNavigationContext(filePath);
|
|
460
|
+
|
|
461
|
+
if (!prev && !next) {
|
|
462
|
+
return '';
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Check if prev/next files exist
|
|
466
|
+
const prevExists = prev && fs.existsSync(prev.file);
|
|
467
|
+
const nextExists = next && fs.existsSync(next.file);
|
|
468
|
+
|
|
469
|
+
const prevHtml = prev ? (prevExists ? `
|
|
470
|
+
<a href="/view?file=${encodeURIComponent(prev.file)}" class="nav-prev">
|
|
471
|
+
<span class="nav-arrow">←</span>
|
|
472
|
+
<span class="nav-label">${prev.name}</span>
|
|
473
|
+
</a>
|
|
474
|
+
` : `
|
|
475
|
+
<span class="nav-prev nav-unavailable" title="Phase planned but not yet implemented">
|
|
476
|
+
<span class="nav-arrow">←</span>
|
|
477
|
+
<span class="nav-label">${prev.name}</span>
|
|
478
|
+
<span class="nav-badge">Planned</span>
|
|
479
|
+
</span>
|
|
480
|
+
`) : '<span></span>';
|
|
481
|
+
|
|
482
|
+
const nextHtml = next ? (nextExists ? `
|
|
483
|
+
<a href="/view?file=${encodeURIComponent(next.file)}" class="nav-next">
|
|
484
|
+
<span class="nav-label">${next.name}</span>
|
|
485
|
+
<span class="nav-arrow">→</span>
|
|
486
|
+
</a>
|
|
487
|
+
` : `
|
|
488
|
+
<span class="nav-next nav-unavailable" title="Phase planned but not yet implemented">
|
|
489
|
+
<span class="nav-label">${next.name}</span>
|
|
490
|
+
<span class="nav-badge">Planned</span>
|
|
491
|
+
<span class="nav-arrow">→</span>
|
|
492
|
+
</span>
|
|
493
|
+
`) : '<span></span>';
|
|
494
|
+
|
|
495
|
+
return `
|
|
496
|
+
<footer class="nav-footer">
|
|
497
|
+
${prevHtml}
|
|
498
|
+
${nextHtml}
|
|
499
|
+
</footer>
|
|
500
|
+
`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
module.exports = {
|
|
504
|
+
detectPlan,
|
|
505
|
+
parsePlanTable,
|
|
506
|
+
getNavigationContext,
|
|
507
|
+
generateNavSidebar,
|
|
508
|
+
generateNavFooter
|
|
509
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port finder utility - finds available port in range
|
|
3
|
+
* Used by markdown-novel-viewer server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const net = require('net');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_PORT = 3456;
|
|
9
|
+
const PORT_RANGE_END = 3500;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a port is available
|
|
13
|
+
* @param {number} port - Port to check
|
|
14
|
+
* @returns {Promise<boolean>} - True if available
|
|
15
|
+
*/
|
|
16
|
+
function isPortAvailable(port) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const server = net.createServer();
|
|
19
|
+
server.once('error', () => resolve(false));
|
|
20
|
+
server.once('listening', () => {
|
|
21
|
+
server.close();
|
|
22
|
+
resolve(true);
|
|
23
|
+
});
|
|
24
|
+
server.listen(port);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Find first available port in range
|
|
30
|
+
* @param {number} startPort - Starting port (default: 3456)
|
|
31
|
+
* @returns {Promise<number>} - Available port
|
|
32
|
+
* @throws {Error} - If no port available in range
|
|
33
|
+
*/
|
|
34
|
+
async function findAvailablePort(startPort = DEFAULT_PORT) {
|
|
35
|
+
for (let port = startPort; port <= PORT_RANGE_END; port++) {
|
|
36
|
+
if (await isPortAvailable(port)) {
|
|
37
|
+
return port;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`No available port in range ${startPort}-${PORT_RANGE_END}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
isPortAvailable,
|
|
45
|
+
findAvailablePort,
|
|
46
|
+
DEFAULT_PORT,
|
|
47
|
+
PORT_RANGE_END
|
|
48
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process manager - handles PID files and server lifecycle
|
|
3
|
+
* Used by markdown-novel-viewer server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const PID_DIR = '/tmp';
|
|
10
|
+
const PID_PREFIX = 'md-novel-viewer-';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get PID file path for a port
|
|
14
|
+
* @param {number} port - Server port
|
|
15
|
+
* @returns {string} - PID file path
|
|
16
|
+
*/
|
|
17
|
+
function getPidFilePath(port) {
|
|
18
|
+
return path.join(PID_DIR, `${PID_PREFIX}${port}.pid`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Write PID file for running server
|
|
23
|
+
* @param {number} port - Server port
|
|
24
|
+
* @param {number} pid - Process ID
|
|
25
|
+
*/
|
|
26
|
+
function writePidFile(port, pid) {
|
|
27
|
+
const pidPath = getPidFilePath(port);
|
|
28
|
+
fs.writeFileSync(pidPath, String(pid));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read PID from file
|
|
33
|
+
* @param {number} port - Server port
|
|
34
|
+
* @returns {number|null} - PID or null if not found
|
|
35
|
+
*/
|
|
36
|
+
function readPidFile(port) {
|
|
37
|
+
const pidPath = getPidFilePath(port);
|
|
38
|
+
if (fs.existsSync(pidPath)) {
|
|
39
|
+
const pid = fs.readFileSync(pidPath, 'utf8').trim();
|
|
40
|
+
return parseInt(pid, 10);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Remove PID file
|
|
47
|
+
* @param {number} port - Server port
|
|
48
|
+
*/
|
|
49
|
+
function removePidFile(port) {
|
|
50
|
+
const pidPath = getPidFilePath(port);
|
|
51
|
+
if (fs.existsSync(pidPath)) {
|
|
52
|
+
fs.unlinkSync(pidPath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Find all running server instances
|
|
58
|
+
* @returns {Array<{port: number, pid: number}>} - Running instances
|
|
59
|
+
*/
|
|
60
|
+
function findRunningInstances() {
|
|
61
|
+
const instances = [];
|
|
62
|
+
const files = fs.readdirSync(PID_DIR);
|
|
63
|
+
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
if (file.startsWith(PID_PREFIX) && file.endsWith('.pid')) {
|
|
66
|
+
const port = parseInt(file.replace(PID_PREFIX, '').replace('.pid', ''), 10);
|
|
67
|
+
const pid = readPidFile(port);
|
|
68
|
+
if (pid) {
|
|
69
|
+
// Check if process is actually running
|
|
70
|
+
try {
|
|
71
|
+
process.kill(pid, 0);
|
|
72
|
+
instances.push({ port, pid });
|
|
73
|
+
} catch {
|
|
74
|
+
// Process not running, clean up stale PID file
|
|
75
|
+
removePidFile(port);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return instances;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Stop server by port
|
|
86
|
+
* @param {number} port - Server port
|
|
87
|
+
* @returns {boolean} - True if stopped successfully
|
|
88
|
+
*/
|
|
89
|
+
function stopServer(port) {
|
|
90
|
+
const pid = readPidFile(port);
|
|
91
|
+
if (!pid) return false;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, 'SIGTERM');
|
|
95
|
+
removePidFile(port);
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
removePidFile(port);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Stop all running servers
|
|
105
|
+
* @returns {number} - Number of servers stopped
|
|
106
|
+
*/
|
|
107
|
+
function stopAllServers() {
|
|
108
|
+
const instances = findRunningInstances();
|
|
109
|
+
let stopped = 0;
|
|
110
|
+
|
|
111
|
+
for (const { port, pid } of instances) {
|
|
112
|
+
try {
|
|
113
|
+
process.kill(pid, 'SIGTERM');
|
|
114
|
+
removePidFile(port);
|
|
115
|
+
stopped++;
|
|
116
|
+
} catch {
|
|
117
|
+
removePidFile(port);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return stopped;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Setup graceful shutdown handlers
|
|
126
|
+
* @param {number} port - Server port
|
|
127
|
+
* @param {Function} cleanup - Additional cleanup function
|
|
128
|
+
*/
|
|
129
|
+
function setupShutdownHandlers(port, cleanup) {
|
|
130
|
+
const handler = (signal) => {
|
|
131
|
+
if (cleanup) cleanup();
|
|
132
|
+
removePidFile(port);
|
|
133
|
+
process.exit(0);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
process.on('SIGTERM', handler);
|
|
137
|
+
process.on('SIGINT', handler);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
getPidFilePath,
|
|
142
|
+
writePidFile,
|
|
143
|
+
readPidFile,
|
|
144
|
+
removePidFile,
|
|
145
|
+
findRunningInstances,
|
|
146
|
+
stopServer,
|
|
147
|
+
stopAllServers,
|
|
148
|
+
setupShutdownHandlers,
|
|
149
|
+
PID_PREFIX
|
|
150
|
+
};
|