feat(v6.3.0): SDD document path unification #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: MUSUBI Reusable Workflow | ||
| permissions: | ||
| contents: read | ||
| issues: write | ||
| pull-requests: write | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| command: | ||
| description: 'MUSUBI command to run' | ||
| type: string | ||
| default: 'validate' | ||
| feature: | ||
| description: 'Feature name to analyze' | ||
| type: string | ||
| required: false | ||
| fail-on-gaps: | ||
| description: 'Fail if gaps found' | ||
| type: boolean | ||
| default: false | ||
| constitution-check: | ||
| description: 'Run constitutional check' | ||
| type: boolean | ||
| default: true | ||
| post-comment: | ||
| description: 'Post comment on PR' | ||
| type: boolean | ||
| default: true | ||
| outputs: | ||
| validation-result: | ||
| description: 'Validation result' | ||
| value: ${{ jobs.musubi.outputs.validation-result }} | ||
| coverage: | ||
| description: 'Coverage percentage' | ||
| value: ${{ jobs.musubi.outputs.coverage }} | ||
| gaps-count: | ||
| description: 'Number of gaps' | ||
| value: ${{ jobs.musubi.outputs.gaps-count }} | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| jobs: | ||
| musubi: | ||
| name: MUSUBI Validation | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| validation-result: ${{ steps.validate.outputs.result }} | ||
| coverage: ${{ steps.analyze.outputs.coverage }} | ||
| gaps-count: ${{ steps.gaps.outputs.count }} | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '18' | ||
| cache: 'npm' | ||
| - name: Install Dependencies | ||
| run: npm ci || npm install | ||
| - name: Install MUSUBI | ||
| run: npm install -g musubi-sdd | ||
| - name: Constitutional Check | ||
| id: constitution | ||
| if: inputs.constitution-check | ||
| run: | | ||
| echo "ποΈ Constitutional Governance Check" | ||
| if [ -f "steering/rules/constitution.md" ]; then | ||
| npx musubi validate --constitution && echo "result=passed" >> $GITHUB_OUTPUT || echo "result=failed" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "β οΈ No constitution.md found" | ||
| echo "result=skipped" >> $GITHUB_OUTPUT | ||
| fi | ||
| - name: Validate | ||
| id: validate | ||
| if: contains(fromJson('["validate", "all"]'), inputs.command) | ||
| run: | | ||
| echo "β Running Validation" | ||
| if npx musubi validate ${{ inputs.feature && format('--feature {0}', inputs.feature) || '' }}; then | ||
| echo "result=passed" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "result=failed" >> $GITHUB_OUTPUT | ||
| exit 1 | ||
| fi | ||
| - name: Analyze | ||
| id: analyze | ||
| if: contains(fromJson('["analyze", "all"]'), inputs.command) | ||
| run: | | ||
| echo "π Running Analysis" | ||
| RESULT=$(npx musubi analyze --format json ${{ inputs.feature && format('--feature {0}', inputs.feature) || '' }} 2>/dev/null || echo '{"coverage":0}') | ||
| COVERAGE=$(echo "$RESULT" | jq -r '.coverage // 0') | ||
| echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT | ||
| echo "Coverage: $COVERAGE%" | ||
| - name: Check Gaps | ||
| id: gaps | ||
| if: contains(fromJson('["gaps", "trace", "all"]'), inputs.command) | ||
| run: | | ||
| echo "π Checking Traceability Gaps" | ||
| RESULT=$(npx musubi gaps --format json ${{ inputs.feature && format('--feature {0}', inputs.feature) || '' }} 2>/dev/null || echo '{"gaps":[]}') | ||
| COUNT=$(echo "$RESULT" | jq '.gaps | length // 0') | ||
| echo "count=$COUNT" >> $GITHUB_OUTPUT | ||
| echo "Gaps: $COUNT" | ||
| if [ "$COUNT" -gt 0 ] && [ "${{ inputs.fail-on-gaps }}" = "true" ]; then | ||
| echo "β Gaps found, failing" | ||
| npx musubi gaps ${{ inputs.feature && format('--feature {0}', inputs.feature) || '' }} | ||
| exit 1 | ||
| fi | ||
| - name: Generate Report | ||
| id: report | ||
| run: | | ||
| cat << 'EOF' > musubi-report.md | ||
| ## π MUSUBI SDD Report | ||
| | Check | Status | | ||
| |-------|--------| | ||
| | Constitutional | ${{ steps.constitution.outputs.result || 'N/A' }} | | ||
| | Validation | ${{ steps.validate.outputs.result || 'N/A' }} | | ||
| | Coverage | ${{ steps.analyze.outputs.coverage || 'N/A' }}% | | ||
| | Gaps | ${{ steps.gaps.outputs.count || 'N/A' }} | | ||
| --- | ||
| *Generated by [MUSUBI SDD](https://github.com/nahisaho/MUSUBI)* | ||
| EOF | ||
| echo "path=musubi-report.md" >> $GITHUB_OUTPUT | ||
| - name: Comment on PR | ||
| if: github.event_name == 'pull_request' && inputs.post-comment | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| const report = fs.readFileSync('musubi-report.md', 'utf8'); | ||
| // Find existing comment | ||
| const { data: comments } = await github.rest.issues.listComments({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| }); | ||
| const botComment = comments.find(c => | ||
| c.user.type === 'Bot' && c.body.includes('MUSUBI SDD Report') | ||
| ); | ||
| if (botComment) { | ||
| await github.rest.issues.updateComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| comment_id: botComment.id, | ||
| body: report | ||
| }); | ||
| } else { | ||
| await github.rest.issues.createComment({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| issue_number: context.issue.number, | ||
| body: report | ||
| }); | ||
| } | ||