From dfff6874191b0c3a3439b0cc131b2b783ce9dfd0 Mon Sep 17 00:00:00 2001 From: "flamingo[bot]" <277372822+flamingo[bot]@users.noreply.github.com> Date: Sat, 14 Nov 2026 03:50:33 +0000 Subject: [PATCH] =?UTF-8?q?chore:=20add=20=F0=9F=A6=A9=20Flamingo=20Code?= =?UTF-8?q?=20Documentation=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This workflow enables automated documentation generation using the ๐Ÿฆฉ Flamingo Code Documentation pipeline. It runs on repository_dispatch events triggered by the multi-platform-hub. No secrets required - all credentials are passed securely at runtime. --- .github/workflows/code-documentation.yml | 938 +++++++++-------------- 1 file changed, 356 insertions(+), 582 deletions(-) diff --git a/.github/workflows/code-documentation.yml b/.github/workflows/code-documentation.yml index d614d52b..134556fd 100644 --- a/.github/workflows/code-documentation.yml +++ b/.github/workflows/code-documentation.yml @@ -10,14 +10,15 @@ # re-dispatch, and on a manual run with graph_only=true. # doc-pipeline The documentation run, dispatched by the hub: # Stage 0 code graph (same build as the code-graph job) -# Stage 1 inline docs, one hidden .md beside each source file -# Stage 2 reference docs: CodeWiki, or the Claude +# (inline docs are retired: every run +# deletes the ones still committed, "Remove retired inline docs") +# Stage 1 reference docs: CodeWiki, or the Claude # architecture analysis where CodeWiki cannot parse # the primary language -# Stage 3 tutorials (getting started, development) -# Stage 4 repository docs: README, CONTRIBUTING, docs index +# Stage 2 tutorials (getting started, development) +# Stage 3 repository docs: README, CONTRIBUTING, docs index # -# Stages 2 (Claude), 3 and 4 follow the code reviewer's agentic pattern +# Stages 1 (Claude), 2 and 3 follow the code reviewer's agentic pattern # (templates/scripts/code-documentation-lib.mjs): the hub serves this # repository's settings, the script packs the material, ONE hub call writes # ONE document (forced `emit_document`; the hub's read tools once the @@ -32,10 +33,10 @@ # # Repository secrets (Settings > Secrets and variables > Actions) # FLAMINGO_HUB_SECRET Required. Authenticates every hub call. -# ANTHROPIC_API_KEY CodeWiki (stage 2) only; every other stage calls +# ANTHROPIC_API_KEY CodeWiki (stage 1) only; every other stage calls # Claude through the hub. -# OPENAI_API_KEY CodeWiki (stage 2). -# YOUTUBE_API_KEY Optional. YouTube embeds in stages 3 and 4. +# OPENAI_API_KEY CodeWiki (stage 1). +# YOUTUBE_API_KEY Optional. YouTube embeds in stages 2 and 3. name: ๐Ÿฆฉ Flamingo Code Documentation @@ -87,10 +88,6 @@ on: stages: description: 'Pipeline stages to execute' required: true - dependencies: - description: 'Comma-separated dependency repos' - required: false - default: '' source_branch: description: 'Branch to analyze code from' required: true @@ -98,7 +95,7 @@ on: description: 'Max source files to process (0 = unlimited)' required: true claude_model: - description: 'Claude model ID for Stage 1/3/4 + Stage 2 Claude-arch fallback (SSOT from hub)' + description: 'Claude model ID for Stages 2 and 3 + the Stage 1 Claude-architecture fallback (SSOT from hub)' required: true codewiki_config: description: 'Complete CodeWiki configuration (per-phase models, engine, stages, depth)' @@ -126,7 +123,7 @@ on: stage_count: description: 'Total number of pipeline stages' required: false - default: '4' + default: '3' graph_only: description: 'true = run only the code-graph job (no documentation run)' required: false @@ -149,20 +146,19 @@ env: GRAPH_ONLY: ${{ github.event.client_payload.graph_only || github.event.inputs.graph_only || 'false' }} # No literal fallback: the stage list lives in CODE_DOCUMENTATION_STAGES on the # hub and is lifted into the payload per repo. A literal here would silently - # restore all four stages on a payload gap, and "Remove docs this run + # restore every stage on a payload gap, and "Remove docs this run # regenerates" would already have wiped the docs tree: the run would delete # docs and regenerate nothing. STAGES: ${{ github.event.client_payload.stages || github.event.inputs.stages || '' }} - DEPENDENCIES: ${{ github.event.client_payload.dependencies || github.event.inputs.dependencies || '' }} - STAGE_COUNT: ${{ github.event.client_payload.stage_count || github.event.inputs.stage_count || '4' }} + STAGE_COUNT: ${{ github.event.client_payload.stage_count || github.event.inputs.stage_count || '3' }} # Branch to checkout for code analysis (github_branch from repo config) SOURCE_BRANCH: ${{ github.event.client_payload.source_branch || github.event.inputs.source_branch || 'main' }} # Debug/testing: limit total source files to analyze (0=unlimited) # Files beyond this limit are DELETED - all stages then process remaining files SOURCE_FILES_LIMIT: ${{ github.event.client_payload.source_files_limit || github.event.inputs.source_files_limit || '0' }} # Claude model SSOT: the hub's CODE_DOCUMENTATION_DEFAULT_MODEL - # (lib/constants/ai-models.ts). The stage 1, 3 and 4 generators and the - # stage 2 Claude analysis all read this variable; no shipped script holds a + # (lib/constants/ai-models.ts). The stage 2 and 3 generators and the + # stage 1 Claude analysis all read this variable; no shipped script holds a # literal model id, and every one throws if CLAUDE_MODEL is empty. Re-run # "Setup Workflow" after bumping the hub-side constant to propagate it. # There is deliberately NO companion request-shape variable: every stage @@ -180,22 +176,17 @@ env: CODEWIKI_CONFIG_JSON: ${{ github.event.client_payload.codewiki_config || github.event.inputs.codewiki_config || '' }} OUTPUT_PATHS_JSON: ${{ github.event.client_payload.output_paths || github.event.inputs.output_paths || '' }} # Stage timeouts (in hours) โ€” single source of truth, downstream steps reference - # `env.STAGE_TIMEOUT_HOURS` directly. Stage 4 is the exception (1h vs 24h cap). + # `env.STAGE_TIMEOUT_HOURS` directly. Stage 3 is the exception (1h vs 24h cap). STAGE_TIMEOUT_HOURS: ${{ github.event.client_payload.timeout || github.event.inputs.timeout || '24' }} # Aliases preserved for downstream step env: keys (they reference these names # by string). All resolve to the same single source. STAGE1_TIMEOUT_HOURS: ${{ github.event.client_payload.timeout || github.event.inputs.timeout || '24' }} - # Stage 1 incremental push: commit + push the PR branch every N generated inline - # docs. A 24h stage that gets cancelled used to lose ALL of its work because the - # only commit happened after the generator returned. Bound the loss to N files. - STAGE1_PUSH_INTERVAL: ${{ github.event.client_payload.stage1_push_interval || '100' }} STAGE2_TIMEOUT_HOURS: ${{ github.event.client_payload.timeout || github.event.inputs.timeout || '24' }} - STAGE3_TIMEOUT_HOURS: ${{ github.event.client_payload.timeout || github.event.inputs.timeout || '24' }} - # Stage 4: Repository Documentation + # Stage 3: Repository Documentation TEMPLATE_REPO: ${{ github.event.client_payload.template_repo || 'flamingo-stack/openframe-oss-tenant' }} TEMPLATE_BRANCH: ${{ github.event.client_payload.template_branch || 'main' }} - STAGE4_TIMEOUT_HOURS: ${{ github.event.client_payload.stage4_timeout || '1' }} - # YouTube Integration (Stage 3 + Stage 4) - JSONB configuration (API key from secrets) + STAGE3_TIMEOUT_HOURS: ${{ github.event.client_payload.stage3_timeout || '1' }} + # YouTube Integration (Stage 2 + Stage 3) - JSONB configuration (API key from secrets) YOUTUBE_CONFIG_JSON: ${{ github.event.client_payload.youtube_config || github.event.inputs.youtube_config || '' }} # README Configuration - JSONB configuration for logo branding README_CONFIG_JSON: ${{ github.event.client_payload.readme_config || github.event.inputs.readme_config || '' }} @@ -211,8 +202,8 @@ env: GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} # e.g., "flamingo-stack" GITHUB_SERVER_URL: ${{ github.server_url }} # e.g., "https://github.com" # Analysis Exclusions - Complete array of glob patterns to exclude from repository analysis - # (build artifacts, dependencies, the hub's own checkout, cloned dependency repos) - EXCLUDED_PATHS: '**/node_modules/**,**/.git/**,**/target/**,**/dist/**,**/build/**,**/.next/**,**/out/**,**/coverage/**,**/vendor/**,**/.yalc/**,**/.turbo/**,**/.gradle/**,**/__pycache__/**,**/.terraform/**,**/.venv/**,**/venv/**,**/multi-platform-hub/**,**/deps-*/**' + # (build artifacts, dependencies, the hub's own checkout) + EXCLUDED_PATHS: '**/node_modules/**,**/.git/**,**/target/**,**/dist/**,**/build/**,**/.next/**,**/out/**,**/coverage/**,**/vendor/**,**/.yalc/**,**/.turbo/**,**/.gradle/**,**/__pycache__/**,**/.terraform/**,**/.venv/**,**/venv/**,**/multi-platform-hub/**' # The documentation pull request's branch prefix (CODE_DOCUMENTATION_BRANCH_PREFIX, written by npm run workflows:sync) DOCS_BRANCH_PREFIX: 'code-documentation/' README_LOGO_ALT: 'OpenFrame Logo' @@ -501,7 +492,7 @@ jobs: run: node /tmp/code-graph-build.mjs # =========================================================================== - # DOCUMENTATION PIPELINE: stages 0 to 4, one pull request per run. Every + # DOCUMENTATION PIPELINE: stages 0 to 3, one pull request per run. Every # stage commits and pushes its own output as it finishes, so a timeout or a # cancellation loses at most the stage in flight. # =========================================================================== @@ -671,7 +662,7 @@ jobs: \"status\": \"running\", \"workflow_run_id\": ${{ github.run_id }}, \"workflow_url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\", - \"current_stage\": \"inline-docs\" + \"current_stage\": \"${STAGES%%,*}\" }" HTTP_CODE=$(send_webhook "$CALLBACK_URL" "$WEBHOOK_SECRET" "$PAYLOAD" "/tmp/webhook_start_response.txt") || HTTP_CODE="failed" @@ -827,10 +818,10 @@ jobs: { echo "VALIDATION_RULES_PATH=/tmp/markdown-validation-rules.md" echo "GUIDELINES_PATH=/tmp/flamingo-markdown-guidelines.md" + echo "STAGE2_FILES_TRACKER=/tmp/stage2-files.txt" + echo "STAGE2_STATS_FILE=/tmp/.doc-stage2-stats.json" echo "STAGE3_FILES_TRACKER=/tmp/stage3-files.txt" - echo "STAGE3_STATS_FILE=/tmp/.doc-stage3-stats.json" - echo "STAGE4_FILES_TRACKER=/tmp/stage4-files.txt" - echo "LEGAL_FILES_LIST=/tmp/stage4-legal-files.txt" + echo "LEGAL_FILES_LIST=/tmp/stage3-legal-files.txt" } >> "$GITHUB_ENV" # Flamingo Markdown guidelines (their own endpoint). REQUIRED: the @@ -873,67 +864,9 @@ jobs: - name: Record source head run: echo "SOURCE_HEAD_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV" - # ========================================================================= - # DEPENDENCY REPOSITORIES (when configured): cloned beside the checkout as - # context for every stage. - # ========================================================================= - - name: Clone dependency repositories - if: env.DEPENDENCIES != '' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Authenticates the clone-token request to the hub (masked by the runner). - WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} - run: | - source /tmp/workflow-helpers.sh - - echo "Cloning dependency repositories: $DEPENDENCIES" - ensure_directory "../deps" - - # The clone credential is minted by the hub for THIS run: read-only, - # scoped to exactly this repository's dependencies, valid one hour. - # A stored repository secret cannot hold it (an installation token - # expires an hour after it is written). - CLONE_TOKEN="" - TOKEN_FILE=$(mktemp) && chmod 600 "$TOKEN_FILE" - HTTP_CODE=$(node /tmp/ci-hub.mjs get "/api/ci/code-documentation/clone-token?github=${GITHUB_REPOSITORY}" "$TOKEN_FILE") || HTTP_CODE="000" - if [ "$HTTP_CODE" = "200" ]; then - CLONE_TOKEN=$(jq -r '.token // empty' "$TOKEN_FILE") - fi - rm -f "$TOKEN_FILE" - if [ -n "$CLONE_TOKEN" ]; then - echo "::add-mask::$CLONE_TOKEN" - echo "Credential: a read-only token the hub minted for this run's dependencies" - else - echo "::warning title=No clone token::The hub did not mint a clone token (HTTP $HTTP_CODE); using GITHUB_TOKEN, which reads public repositories only." - CLONE_TOKEN="$GH_TOKEN" - fi - - # The credential applies to THESE clones only (`git -c`), never globally: - # the token reads the dependencies and nothing else, so a global URL - # rewrite would also send this job's own pushes through it. - IFS=',' read -ra DEPS <<< "$DEPENDENCIES" - for dep in "${DEPS[@]}"; do - repo_name=$(basename "$dep") - echo "::group::Clone $dep into ../deps/$repo_name" - if git -c "url.https://x-access-token:${CLONE_TOKEN}@github.com/.insteadOf=https://github.com/" \ - clone --depth 1 "https://github.com/$dep.git" "../deps/$repo_name" 2>&1; then - git -C "../deps/$repo_name" remote set-url origin "https://github.com/$dep.git" - file_count=$(node /tmp/ci-source.mjs count "../deps/$repo_name" 2>/dev/null || echo "?") - echo "Cloned $dep ($file_count source files)" - else - echo "::warning title=Dependency not cloned::Could not clone $dep." - fi - echo "::endgroup::" - done - - echo "::group::Dependency directories" - ls -la ../deps/ 2>/dev/null || echo "No dependencies cloned" - echo "::endgroup::" - echo "Dependency source files available as context: $(node /tmp/ci-source.mjs count ../deps 2>/dev/null || echo '?')" - # ========================================================================= # PRIMARY LANGUAGE (before every stage, so all of them agree): picks the - # stage 2 engine and filters stages 1 to 3. + # stage 1 engine and filters every stage's source files. # ========================================================================= - name: Detect primary language id: detect_language @@ -948,7 +881,7 @@ jobs: exit 1 fi - echo "Detecting the primary language across the repository (.) and its dependencies (../deps/)" + echo "Detecting the primary language of the repository" # ONE detection, from the vocabulary the hub serves (ci-source.mjs): which # languages are source, their extensions, and which of them CodeWiki can @@ -967,16 +900,16 @@ jobs: if [ "$CODEWIKI_SUPPORTED" = "true" ]; then echo "CodeWiki can parse it: yes" else - echo "CodeWiki can parse it: no (stage 2 uses the Claude architecture analysis)" + echo "CodeWiki can parse it: no (stage 1 uses the Claude architecture analysis)" fi # Per-repo engine override. # - # The detection above cannot see mixed repos: it counts `.` AND `../deps`, so - # a Rust or Go product with a TypeScript dependency clones its way past the - # >=10 threshold and runs CodeWiki over a codebase whose analyzers do not - # exist โ€” which yields synthetic module_1/module_2/... docs that look like a - # successful run. `engine` pins the choice. + # The detection above cannot see mixed repos: a Rust or Go product with a + # TypeScript tooling folder passes the >=10 threshold and runs CodeWiki over + # a codebase whose analyzers do not exist, which yields synthetic + # module_1/module_2/... docs that look like a successful run. `engine` pins + # the choice. # # Applied here, before set_output, so all five downstream gates keep reading # one value and need no change. It cannot live in the `if:` conditions: @@ -985,17 +918,17 @@ jobs: case "$CODEWIKI_ENGINE" in claude) CODEWIKI_SUPPORTED="false" - echo "Stage 2 engine: claude (pinned by the repository configuration)" + echo "Stage 1 engine: claude (pinned by the repository configuration)" ;; codewiki) CODEWIKI_SUPPORTED="true" - echo "Stage 2 engine: codewiki (pinned by the repository configuration)" + echo "Stage 1 engine: codewiki (pinned by the repository configuration)" ;; auto) - echo "Stage 2 engine: auto (CodeWiki supported: $CODEWIKI_SUPPORTED)" + echo "Stage 1 engine: auto (CodeWiki supported: $CODEWIKI_SUPPORTED)" ;; *) - echo "::error title=Invalid stage 2 engine::engine is '$CODEWIKI_ENGINE'; expected auto, codewiki or claude." + echo "::error title=Invalid stage 1 engine::engine is '$CODEWIKI_ENGINE'; expected auto, codewiki or claude." exit 1 ;; esac @@ -1301,14 +1234,14 @@ jobs: SOURCE_FILES_LIMIT: ${{ env.SOURCE_FILES_LIMIT }} # DOCS_OUTPUT_PATH is needed below so the find can exclude the # generated docs tree (deleted by the docs-removal step that runs - # AFTER discovery but BEFORE Stage 1 โ€” source-extension files - # under docs/ would otherwise be enumerated, then deleted, then - # cause ENOENT in Stage 1). + # AFTER discovery but BEFORE the stages read the list โ€” source-extension + # files under docs/ would otherwise be enumerated, then deleted, then + # cause ENOENT in the stage that reads them). DOCS_OUTPUT_PATH: ${{ env.DOCS_OUTPUT_PATH }} run: | source /tmp/workflow-helpers.sh - echo "Discovering source files in the repository (.) and its dependencies (../deps/)" + echo "Discovering source files in the repository" # File paths (all in /tmp to avoid accidental commits) SOURCE_FILES_LIST="/tmp/.code-documentation-source-files.txt" @@ -1318,12 +1251,11 @@ jobs: # 1. Find SOURCE CODE files only (with standard exclusions) # IMPORTANT: exclude $DOCS_OUTPUT_PATH/* โ€” the docs-removal step below - # does `rm -rf $DOCS_OUTPUT_PATH` BEFORE Stage 1 reads this list. If + # does `rm -rf $DOCS_OUTPUT_PATH` BEFORE any stage reads this list. If # any source-extension file lives under the docs tree (Doxygen's # `docs/doxygen/documentation.h`, Sphinx `_extensions/*.py`, etc.), - # it would be enumerated here, then deleted, then Stage 1 hits - # ENOENT trying to read it. This is the source-discovery / - # docs-removal / Stage-1 ordering bug โ€” exclusion is the targeted fix. + # it would be enumerated here, then deleted, then a stage hits + # ENOENT trying to read it. Exclusion is the targeted fix. # The list comes from ci-source.mjs: the served source extensions, the # served never-source directories, tests skipped by name โ€” the same rule # the language detection above counted with. @@ -1344,7 +1276,7 @@ jobs: KEEPING=$(wc -l < "$SOURCE_FILES_LIST" | tr -d ' ') # 2. Find ALL files in the repo (except .git and workflow temp files) - find . ../deps -type f \ + find . -type f \ -not -path "*/.git/*" \ -not -path "*/.git" \ -not -name ".code-documentation-*" \ @@ -1412,7 +1344,9 @@ jobs: # Show what's left echo "Remaining directories (first 20):" - find . -type d -not -path "*/.git/*" -not -path "*/.git" | head -20 + # Never `| head`: under the runner's pipefail, head exiting early kills the writer (SIGPIPE) and + # fails the step once the listing outgrows the pipe buffer. `sed -n '1,Np'` reads it all. + find . -type d -not -path "*/.git/*" -not -path "*/.git" | sed -n '1,20p' echo "::endgroup::" else # No limit - keep all source files @@ -1424,10 +1358,8 @@ jobs: # Count remaining files FILE_COUNT=$(count_source_files "$SOURCE_FILES_LIST") - MAIN_COUNT=$(count_main_repo_files "$SOURCE_FILES_LIST") - DEPS_COUNT=$(count_dependency_files "$SOURCE_FILES_LIST") - echo "Source files to document: $FILE_COUNT ($MAIN_COUNT in this repository, $DEPS_COUNT in dependencies)" + echo "Source files to document: $FILE_COUNT" echo "::group::Source files by language" node /tmp/ci-source.mjs breakdown "$SOURCE_FILES_LIST" | sed 's/^/ /' @@ -1541,10 +1473,9 @@ jobs: echo "This PR will be updated as each documentation stage completes." echo "" echo "Progress" - echo "- Stage 1 Inline Documentation - Starting..." - echo "- Stage 2 Architecture Analysis - Pending" - echo "- Stage 3 Tutorial Generation - Pending" - echo "- Stage 4 Repository Documentation - Pending" + echo "- Stage 1 Architecture Analysis - Pending" + echo "- Stage 2 Tutorial Generation - Pending" + echo "- Stage 3 Repository Documentation - Pending" echo "" echo "Generated by ๐Ÿฆฉ Flamingo Code Documentation" } > /tmp/pr-body.md @@ -1593,8 +1524,8 @@ jobs: # SCOPED TO THE CONFIGURED STAGES. # # This used to `rm -rf $DOCS_OUTPUT_PATH` unconditionally. That was safe only - # while every repo ran all four stages. With per-repo `stages`, wiping the - # whole tree when Stage 2 is disabled means the reference architecture and + # while every repo ran every stage. With per-repo `stages`, wiping the + # whole tree when Stage 1 is disabled means the reference architecture and # diagrams are deleted and never rebuilt โ€” the pull request becomes a net # DELETION of existing documentation. CLEAN_TARGETS=() @@ -1614,13 +1545,13 @@ jobs: # # Recording the blanket DOCS_OUTPUT_PATH instead (the old behaviour) made the # restore treat docs/ as a single unit that is "safe to leave deleted" only - # once ALL FOUR stages complete โ€” but the restore runs right after Stage 2, - # so Stage 3/4 are never 'completed' yet, and it restored the ENTIRE pre-clean + # once EVERY stage completes โ€” but the restore runs right after Stage 1, + # so Stage 2/3 are never 'completed' yet, and it restored the ENTIRE pre-clean # tree every time, undoing the wipe and resurrecting the orphaned module_N docs. DELETE_TARGETS=("${CLEAN_TARGETS[@]}") FULL_WIPE=false SELECTED_COUNT=$(echo "$STAGES" | tr ',' '\n' | grep -c .) - if [ "$SELECTED_COUNT" -ge "${STAGE_COUNT:-4}" ]; then + if [ "$SELECTED_COUNT" -ge "${STAGE_COUNT:-3}" ]; then FULL_WIPE=true DELETE_TARGETS=("$DOCS_OUTPUT_PATH") # Record every stage-owned subtree (NOT the blanket docs/) so the restore @@ -1677,6 +1608,44 @@ jobs: fi fi + # ========================================================================= + # REMOVE RETIRED INLINE DOCS + # A retired earlier stage wrote one hidden `..md` beside each source file, + # its first line ``. The stage is gone, and every + # run deletes the ones still committed. Tracked files only (`git ls-files`), + # outside node_modules; the first-line marker is what makes a file one of + # them, never its name alone. Committed here: the later commit steps stage + # .md files they find, never deletions. No file, no commit. + # ========================================================================= + - name: Remove retired inline docs + env: + BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} + run: | + source /tmp/workflow-helpers.sh + + RETIRED_LIST=/tmp/retired-inline-docs.txt + : > "$RETIRED_LIST" + while IFS= read -r -d '' f; do + case "$f" in node_modules/*|*/node_modules/*) continue ;; esac + case "${f##*/}" in .?*.md) ;; *) continue ;; esac + [ -f "$f" ] || continue + if head -n 1 -- "$f" | grep -Eq '^'; then + printf '%s\n' "$f" >> "$RETIRED_LIST" + fi + done < <(git ls-files -z -- '*.md') + + RETIRED_COUNT=$(wc -l < "$RETIRED_LIST" | tr -d ' ') + if [ "$RETIRED_COUNT" -eq 0 ]; then + echo "No retired inline docs in this repository" + exit 0 + fi + + echo "Removing $RETIRED_COUNT retired inline docs" + GIT_LITERAL_PATHSPECS=1 git rm -q --pathspec-from-file="$RETIRED_LIST" + git commit -m "chore(docs): remove $RETIRED_COUNT retired inline docs [skip ci]" + git push origin "$BRANCH_NAME" + echo "Committed and pushed the removal of $RETIRED_COUNT files" + - name: Set up Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: @@ -1707,224 +1676,12 @@ jobs: CODE_GRAPH_COMMIT_SHA: ${{ env.SOURCE_HEAD_SHA }} run: node /tmp/code-graph-build.mjs - # ========================================================================= - # STAGE 1: INLINE DOCS - # One hidden .md beside each source file, over the discovered file list. - # ========================================================================= - - name: Install stage 1 dependencies - if: contains(env.STAGES, 'inline-docs') - # Install generator deps in an ISOLATED tree under RUNNER_TEMP, NOT the target - # repo. npm resolves against an empty package.json here, so a target repo's own - # peer conflicts (e.g. react-accessible-accordion vs react 18) can never make this - # fail. No --legacy-peer-deps / --no-save band-aids. Generators find these via the - # NODE_PATH set on the generate step (RUNNER_TEMP/doc-orch-deps/node_modules). - run: | - mkdir -p "$RUNNER_TEMP/doc-orch-deps" && cd "$RUNNER_TEMP/doc-orch-deps" - npm init -y >/dev/null 2>&1 - npm install @anthropic-ai/sdk@0.115.0 zod@3.25.76 glob@13.0.6 - - - name: Generate inline docs (stage 1) - id: stage1 - if: contains(env.STAGES, 'inline-docs') - env: - # SECURITY: Pass secrets per-step with inline masking. - # No ANTHROPIC_API_KEY โ€” this stage calls Claude through the hub - # (/api/ci/claude), which the WEBHOOK_SECRET below authenticates. - WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} - # Claude model SSOT โ€” see workflow env CLAUDE_MODEL block - CLAUDE_MODEL: ${{ env.CLAUDE_MODEL }} - DOCS_OUTPUT_PATH: ${{ env.DOCS_OUTPUT_PATH }} - # Stage timeout (in hours) - STAGE1_TIMEOUT_HOURS: ${{ env.STAGE1_TIMEOUT_HOURS }} - # Incremental commit + push every N generated docs (see workflow env) - STAGE1_PUSH_INTERVAL: ${{ env.STAGE1_PUSH_INTERVAL }} - BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} - STAGE1_PUSH_MARKER: /tmp/stage1-progress-pushes - # Unified file discovery result (single source of truth) - SOURCE_FILES_LIST: ${{ steps.discover_files.outputs.source_files_list }} - SOURCE_FILE_COUNT: ${{ steps.discover_files.outputs.source_file_count }} - # NODE_PATH to find modules from /tmp/ scripts - NODE_PATH: ${{ runner.temp }}/doc-orch-deps/node_modules - # Custom AI Instructions (All Stages) - CUSTOM_REPO_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }} - # External Repositories - EXTERNAL_REPOS: ${{ env.EXTERNAL_REPOS }} - run: | - source /tmp/workflow-helpers.sh - - echo "Source files: $SOURCE_FILE_COUNT (from $SOURCE_FILES_LIST)" - echo "Progress push: every $STAGE1_PUSH_INTERVAL generated docs, to $BRANCH_NAME" - - # Fresh marker: the generator appends one line per progress push, and the - # commit step below reads it to know work was already pushed. - rm -f "$STAGE1_PUSH_MARKER" - - # Script already downloaded to /tmp/ in setup step. run_stage records - # the outcome as stage1_status โ€” see its note in workflow-helpers.sh. - run_stage "Stage 1" "$STAGE1_TIMEOUT_HOURS" stage1_status node /tmp/generate-inline-docs.cjs - - # Count generated files (hidden .*.md files) - INLINE_DOCS=$(find . -name ".*.md" -newer .git -type f -not -path "./node_modules/*" -not -path "./.git/*" | wc -l) - set_output "stage1_files" "$INLINE_DOCS" - - # ========================================================================= - # COMMIT STAGE 1 RESULTS (progressive pull request) - # ========================================================================= - # `!= ''`, not `== 'completed'`: runs on a FAILED stage too โ€” see run_stage - # in workflow-helpers.sh (partial output is worth committing; the status - # is what reports the truth home). The build gate holds every run_stage - # commit step to this predicate. - - name: Commit and push stage 1 results - if: always() && steps.stage1.outputs.stage1_status != '' - id: commit-stage1 - env: - BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} - STAGE_FILES: ${{ steps.stage1.outputs.stage1_files }} - STAGE1_PUSH_MARKER: /tmp/stage1-progress-pushes - run: | - source /tmp/workflow-helpers.sh - - # The generator already commits + pushes every N docs (STAGE1_PUSH_INTERVAL). - # Report how much landed that way; what's left here is the final partial batch. - if [ -s "$STAGE1_PUSH_MARKER" ]; then - PUSHED_BATCHES=$(wc -l < "$STAGE1_PUSH_MARKER" | tr -d ' ') - PUSHED_FILES=$(awk '{ sum += $1 } END { print sum + 0 }' "$STAGE1_PUSH_MARKER") - echo "Already pushed during generation: $PUSHED_FILES files in $PUSHED_BATCHES batches" - fi - - # Push any commits the generator made but could not push (transient push failure) - git push origin "HEAD:refs/heads/$BRANCH_NAME" 2>/dev/null || true - - # Stage all .md files generated by Stage 1 (hidden inline docs) - find . -name ".*.md" -type f \ - -not -path "./node_modules/*" \ - -not -path "./.git/*" \ - -exec git add -f {} \; 2>/dev/null || true - - # Check if there are changes - STAGED_COUNT=$(git diff --cached --name-only | wc -l) - - if [ "$STAGED_COUNT" -gt 0 ]; then - # Commit and push - git commit -m "docs: Stage 1 - Inline documentation ($STAGE_FILES files) [skip ci]" - git push origin "$BRANCH_NAME" - - echo "Committed and pushed $STAGED_COUNT stage 1 files" - set_output "committed" "true" - elif [ -s "$STAGE1_PUSH_MARKER" ]; then - # Everything already landed via the incremental progress pushes - echo "Nothing left to commit: every stage 1 file was pushed during generation" - set_output "committed" "true" - else - echo "::warning title=Stage 1 produced nothing::No inline docs to commit." - set_output "committed" "false" - fi - - # ========================================================================= - # ORPHANED INLINE DOCS - # Right after stage 1: removes each hidden .*.md whose source file is gone. - # ========================================================================= - # `== 'completed'` HERE IS DELIBERATE, unlike the commit steps: deleting - # "orphaned" docs after a stage that FAILED would delete docs whose - # sources were never re-examined. - - name: Remove orphaned inline docs - if: always() && steps.stage1.outputs.stage1_status == 'completed' - id: orphan-detection-inline - env: - BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} - run: | - source /tmp/workflow-helpers.sh - - # Run orphan detection script - bash /tmp/detect-orphans.sh || true - - # Check if orphans were deleted (script writes list to /tmp/orphaned-inline-files.txt) - DELETED_FILES_LIST="/tmp/orphaned-inline-files.txt" - - if [ -f "$DELETED_FILES_LIST" ] && [ -s "$DELETED_FILES_LIST" ]; then - DELETED_COUNT=$(wc -l < "$DELETED_FILES_LIST" | tr -d ' ') - echo "Orphaned inline docs removed: $DELETED_COUNT" - - # Only add the specific files that were deleted by the script - while IFS= read -r deleted_file; do - git add "$deleted_file" 2>/dev/null || true - done < "$DELETED_FILES_LIST" - - # Verify we have staged changes - STAGED_COUNT=$(git diff --cached --name-only | wc -l | tr -d ' ') - - if [ "$STAGED_COUNT" -gt 0 ]; then - git commit -m "chore(docs): Remove $DELETED_COUNT orphaned inline files [skip ci]" - git push origin "$BRANCH_NAME" - echo "Committed $STAGED_COUNT orphan removals" - set_output "orphans_deleted" "$STAGED_COUNT" - else - echo "Nothing to commit (the files were already absent)" - set_output "orphans_deleted" "0" - fi - else - echo "No orphaned inline docs" - set_output "orphans_deleted" "0" - fi - - - name: Update pull request with stage 1 progress - if: always() && steps.commit-stage1.outputs.committed == 'true' && steps.create-initial-pr.outputs.pull-request-number - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files }} - RUN_ID_VAR: ${{ env.RUN_ID }} - REPO_NAME: ${{ github.repository }} - run: | - source /tmp/workflow-helpers.sh - - # Update PR body - PR_BODY="## ๐Ÿฆฉ Flamingo Code Documentation: In Progress - - **Run ID:** \`$RUN_ID_VAR\` - **Status:** ๐Ÿ”„ Running... - - This PR is being updated as each documentation stage completes. - - ### Progress - - โœ… Stage 1: Inline Documentation - Completed ($STAGE1_FILES files) - - โณ Stage 2: Architecture Analysis - Running... - - โฑ๏ธ Stage 3: Tutorial Generation - Pending - - โฑ๏ธ Stage 4: Repository Documentation - Pending - - --- - ๐Ÿฆฉ Generated by [Flamingo Code Documentation](https://flamingo.run)" - - gh pr edit "$PR_NUMBER" --body "$PR_BODY" - - echo "Pull request #$PR_NUMBER updated with stage 1 progress" - - - name: Report stage 1 progress - if: always() && contains(env.STAGES, 'inline-docs') && env.HUB_BASE_URL != '' - continue-on-error: true - env: - WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} - WORKFLOW_RUN_ID: ${{ github.run_id }} - WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || 'skipped' }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - PR_URL: ${{ steps.create-initial-pr.outputs.pull-request-url }} - PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} - run: | - source /tmp/workflow-helpers.sh - - CALLBACK_URL="${HUB_BASE_URL}/api/code-documentation/webhook" - echo "Reporting stage 1 (inline docs): $STAGE1_STATUS, $STAGE1_FILES files" - report_stage_progress "$CALLBACK_URL" "$WEBHOOK_SECRET" "$RUN_ID" "$REPO_ID" \ - "$WORKFLOW_RUN_ID" "$WORKFLOW_URL" "codewiki" \ - "$STAGE1_STATUS" "$STAGE1_FILES" "" "0" "" "0" "" "0" \ - "$PR_URL" "$PR_NUMBER" - # ========================================================================= # ECOSYSTEM FACTS โ€” derived by the hub from the code graph, never written # by a model. Two renderings of the same live snapshot: ecosystem.md - # (committed under the reference tree and fed to the Stage 2/3/4 prompts - # as ground truth for the Dependencies sections) and the marker-delimited + # (committed under the reference tree and fed to every stage's prompts, CodeWiki's + # included, as ground truth for the Dependencies sections; fetched right after + # this run's own graph build, so the facts are this commit's) and the marker-delimited # AGENTS.md block (upserted in place, idempotent). A repo with no graph # yet is a notice, not a failure. # ========================================================================= @@ -1955,13 +1712,13 @@ jobs: rm -f /tmp/ecosystem.md exit 0 fi - # Kept in /tmp ONLY until Stage 2 has run. The reference directory is + # Kept in /tmp ONLY until Stage 1 has run. The reference directory is # CodeWiki's output directory, and CodeWiki asks "already contains # documentation. Overwrite?" when it finds a .md file there โ€” a prompt - # a runner cannot answer, so Stage 2 aborted on every CodeWiki repo - # (CodeWiki run 35293807658). The Stage 2 commit step copies the file + # a runner cannot answer, so Stage 1 aborted on every CodeWiki repo + # (CodeWiki run 35293807658). The Stage 1 commit step copies the file # into place, after either engine has written its own output. - echo "Fetched ecosystem.md ($(wc -c < /tmp/ecosystem.md | tr -d ' ') bytes); it is copied to $REFERENCE_OUTPUT_PATH after stage 2" + echo "Fetched ecosystem.md ($(wc -c < /tmp/ecosystem.md | tr -d ' ') bytes); it is copied to $REFERENCE_OUTPUT_PATH after stage 1" HTTP_CODE=$(node /tmp/ci-hub.mjs get "${ECOSYSTEM_PATH}&format=agents" /tmp/ecosystem-agents.md) || HTTP_CODE="000" if [ "$HTTP_CODE" = "200" ]; then @@ -1983,10 +1740,9 @@ jobs: fi # ========================================================================= - # STAGE 2: REFERENCE DOCS + # STAGE 1: REFERENCE DOCS # Architecture overview, module tree and diagrams. CodeWiki where it can - # parse the primary language ("Detect primary language" decided, before - # stage 1), otherwise the Claude architecture analysis. + # parse the primary language ("Detect primary language" decided), otherwise the Claude architecture analysis. # ========================================================================= - name: Set up Python 3.12 @@ -2052,7 +1808,7 @@ jobs: PYTHON_KEYRING_BACKEND: keyrings.alt.file.PlaintextKeyring # Flamingo Markdown Guidelines path (needed for module import during config/validate) FLAMINGO_MARKDOWN_GUIDELINES_PATH: /tmp/flamingo-markdown-guidelines.md - # OSS Tenant Structure: Stage 2 outputs (for clean slate deletion) + # OSS Tenant Structure: Stage 1 outputs (for clean slate deletion) REFERENCE_OUTPUT_PATH: ${{ env.REFERENCE_OUTPUT_PATH }} DIAGRAMS_OUTPUT_PATH: ${{ env.DIAGRAMS_OUTPUT_PATH }} run: | @@ -2149,8 +1905,8 @@ jobs: echo "" set_output "codewiki_configured" "true" - - name: Generate reference docs with CodeWiki (stage 2) - id: stage2 + - name: Generate reference docs with CodeWiki (stage 1) + id: stage1 if: contains(env.STAGES, 'codewiki') && steps.detect_language.outputs.codewiki_supported == 'true' && steps.codewiki_config.outputs.codewiki_configured == 'true' continue-on-error: false env: @@ -2159,11 +1915,11 @@ jobs: # API keys for both providers (CodeWiki will use the one configured) OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - # OSS Tenant Structure: Stage 2 outputs + # OSS Tenant Structure: Stage 1 outputs REFERENCE_OUTPUT_PATH: ${{ env.REFERENCE_OUTPUT_PATH }} DIAGRAMS_OUTPUT_PATH: ${{ env.DIAGRAMS_OUTPUT_PATH }} # Stage timeout - STAGE2_TIMEOUT_HOURS: ${{ env.STAGE2_TIMEOUT_HOURS }} + STAGE1_TIMEOUT_HOURS: ${{ env.STAGE1_TIMEOUT_HOURS }} # CodeWiki JSON configuration (required for unified function) CODEWIKI_CONFIG_JSON: ${{ env.CODEWIKI_CONFIG_JSON }} # CodeWiki model configuration (cluster, generation, fallback) @@ -2188,8 +1944,14 @@ jobs: CUSTOM_REPO_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }} # External Repositories EXTERNAL_REPOS: ${{ env.EXTERNAL_REPOS }} - # Dependencies (for CodeWiki multi-path support) - DEPENDENCIES: ${{ env.DEPENDENCIES }} + # The code-graph tools CodeWiki's writing agents call for another repository's code + # (`/api/ci/code-documentation/tools`, with HUB_BASE_URL and GITHUB_REPOSITORY from the job env). + WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} + # What CodeWiki spends, per answering model, merged into the run's usage artifact + # (the file report_final_status sends; workflow-helpers.sh reads the same default path). + CODEWIKI_USAGE_FILE: /tmp/code-documentation-usage.json + # The code graph's dependency facts ("Fetch ecosystem facts"), inlined into every CodeWiki writer prompt. + ECOSYSTEM_FACTS_PATH: /tmp/ecosystem.md run: | # Determine per-provider API keys (same logic as Configure step) if [ "$CODEWIKI_CLUSTER_PROVIDER" = "anthropic" ]; then @@ -2210,39 +1972,12 @@ jobs: export FALLBACK_API_KEY="${{ secrets.OPENAI_API_KEY }}" fi - # Verify dependencies directory before CodeWiki runs - echo "" - echo "๐Ÿ” Pre-CodeWiki Dependency Verification:" - echo " Current directory: $(pwd)" - echo " Absolute path: $(realpath .)" - echo "" - - if [ -d "./deps" ]; then - echo " โœ… ./deps EXISTS" - echo " Contents: $(find ./deps -mindepth 1 -maxdepth 1 | wc -l) repositories" - find ./deps -mindepth 1 -maxdepth 1 | head -5 - else - echo " โŒ ./deps NOT FOUND" - fi - - if [ -d "../deps" ]; then - echo " โœ… ../deps EXISTS" - echo " Absolute: $(realpath ../deps)" - echo " Contents: $(find ../deps -mindepth 1 -maxdepth 1 | wc -l) repositories" - find ../deps -mindepth 1 -maxdepth 1 | head -5 - else - echo " โŒ ../deps NOT FOUND" - fi - - echo " DEPENDENCIES env: ${DEPENDENCIES:-}" - echo "" - # Run externalized CodeWiki analysis script /tmp/run-codewiki-analysis.sh # The alternative for any language CodeWiki cannot parse. - - name: Generate reference docs with Claude (stage 2) - id: stage2_alt + - name: Generate reference docs with Claude (stage 1) + id: stage1_alt if: contains(env.STAGES, 'codewiki') && steps.detect_language.outputs.codewiki_supported == 'false' env: # No ANTHROPIC_API_KEY โ€” this stage calls Claude through the hub @@ -2251,7 +1986,7 @@ jobs: # SSOT โ€” see workflow env CLAUDE_MODEL block CLAUDE_MODEL: ${{ env.CLAUDE_MODEL }} PRIMARY_LANGUAGE: ${{ steps.detect_language.outputs.primary_language }} - # OSS Tenant Structure: Stage 2 outputs + # OSS Tenant Structure: Stage 1 outputs REFERENCE_OUTPUT_PATH: ${{ env.REFERENCE_OUTPUT_PATH }} DIAGRAMS_OUTPUT_PATH: ${{ env.DIAGRAMS_OUTPUT_PATH }} # Unified file discovery result (single source of truth) @@ -2261,8 +1996,6 @@ jobs: CUSTOM_REPO_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }} # External Repositories EXTERNAL_REPOS: ${{ env.EXTERNAL_REPOS }} - # Dependencies (for consistency with other stages) - DEPENDENCIES: ${{ env.DEPENDENCIES }} run: | # Run externalized Claude architecture analysis script /tmp/run-claude-architecture-analysis.sh @@ -2270,9 +2003,9 @@ jobs: # ========================================================================= # RESTORE DOCS NO STAGE REGENERATED # The docs removal took the reference/diagrams trees because `codewiki` was in - # STAGES. If neither Stage-2 variant then completed โ€” a repo with no + # STAGES. If neither Stage-1 variant then completed โ€” a repo with no # discoverable source, an install failure, a skip โ€” the deletion would be the - # only Stage-2 change in the pull request, i.e. a net removal of documentation + # only Stage-1 change in the pull request, i.e. a net removal of documentation # nobody asked to remove. Put it back. # ========================================================================= - name: Restore docs no stage regenerated @@ -2280,22 +2013,27 @@ jobs: env: BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status }} - STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status }} - STAGE2_ALT_STATUS: ${{ steps.stage2_alt.outputs.stage2_status }} + STAGE1_ALT_STATUS: ${{ steps.stage1_alt.outputs.stage1_status }} run: | # The docs removal deleted whatever the configured stages own, and committed that # deletion. Any owned subtree whose stage then produced nothing must be put # back โ€” otherwise the pull request is a net REMOVAL of documentation nobody # asked to remove. Iterates the exact list the removal recorded, so the # stage -> subtree map has one home. + # + # HERE only Stage 1's own trees, before Stages 2 and 3 read them. The trees Stages + # 2 and 3 write, and a full wipe, are judged after Stage 3 ("Restore docs stages 2 + # and 4 did not regenerate"): judged here, before those stages ran, they were + # always put back from the base, so a page Stage 2 refused (or no longer writes) + # stayed in the pull request as the base's old copy. if [ -z "$CLEAN_TARGETS_RECORD" ]; then echo "Nothing was removed; nothing to restore" exit 0 fi - STAGE2_OK=false - [ "$STAGE2_STATUS" = "completed" ] && STAGE2_OK=true - [ "$STAGE2_ALT_STATUS" = "completed" ] && STAGE2_OK=true + STAGE1_OK=false + [ "$STAGE1_STATUS" = "completed" ] && STAGE1_OK=true + [ "$STAGE1_ALT_STATUS" = "completed" ] && STAGE1_OK=true # CodeWiki leaves multi-GB scratch behind on a failed run; never let it near # the index. @@ -2307,15 +2045,10 @@ jobs: # A target is safe to leave deleted only if something regenerated it. case "$target" in "$REFERENCE_OUTPUT_PATH"|"$DIAGRAMS_OUTPUT_PATH") - [ "$STAGE2_OK" = true ] && continue ;; - "$GETTING_STARTED_OUTPUT_PATH"|"$DEVELOPMENT_OUTPUT_PATH") - [ "$STAGE3_STATUS" = "completed" ] && continue ;; - "$DOCS_OUTPUT_PATH") - # Full wipe: only fully safe when every stage delivered. - if [ "$STAGE1_STATUS" = "completed" ] && [ "$STAGE2_OK" = true ] && \ - [ "$STAGE3_STATUS" = "completed" ] && [ "$STAGE4_STATUS" = "completed" ]; then - continue - fi ;; + [ "$STAGE1_OK" = true ] && continue ;; + *) + # Judged after Stage 3. + continue ;; esac if git checkout "$PRE_CLEAN_SHA" -- "$target" 2>/dev/null; then echo "::notice title=Docs restored::$target restored from $PRE_CLEAN_SHA; no stage regenerated it." @@ -2324,8 +2057,8 @@ jobs: done <<< "$CLEAN_TARGETS_RECORD" # `git checkout -- ` already stages exactly those paths. Deliberately NO - # `git add -A`: at this point the workspace holds npm install output from - # Stage 1 and, on a failed CodeWiki run, its scratch trees. + # `git add -A`: at this point the workspace may hold dependency installs and, + # on a failed CodeWiki run, CodeWiki's scratch trees. if [ "$RESTORED" -eq 1 ] && [ -n "$(git diff --cached --name-only)" ]; then git commit -m "chore(docs): restore documentation no stage regenerated [skip ci]" git push origin "$BRANCH_NAME" @@ -2335,14 +2068,14 @@ jobs: fi # ========================================================================= - # COMMIT STAGE 2 RESULTS (progressive pull request) + # COMMIT STAGE 1 RESULTS (progressive pull request) # ========================================================================= - - name: Commit and push stage 2 results - if: always() && (steps.stage2.outputs.stage2_status == 'completed' || steps.stage2_alt.outputs.stage2_status == 'completed') - id: commit-stage2 + - name: Commit and push stage 1 results + if: always() && (steps.stage1.outputs.stage1_status == 'completed' || steps.stage1_alt.outputs.stage1_status == 'completed') + id: commit-stage1 env: BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files }} REFERENCE_OUTPUT_PATH: ${{ env.REFERENCE_OUTPUT_PATH }} DIAGRAMS_OUTPUT_PATH: ${{ env.DIAGRAMS_OUTPUT_PATH }} run: | @@ -2376,10 +2109,10 @@ jobs: fi done - echo "::group::Stage 2 output before staging" + echo "::group::Stage 1 output before staging" echo "Reference directory ($REFERENCE_OUTPUT_PATH):" if [ -d "$REFERENCE_OUTPUT_PATH" ]; then - find "$REFERENCE_OUTPUT_PATH" -type f \( -name "*.md" -o -name "*.mmd" -o -name ".gitignore" \) | head -20 + find "$REFERENCE_OUTPUT_PATH" -type f \( -name "*.md" -o -name "*.mmd" -o -name ".gitignore" \) | sed -n '1,20p' FILE_COUNT=$(find "$REFERENCE_OUTPUT_PATH" -type f \( -name "*.md" -o -name "*.mmd" \) | wc -l | tr -d ' ') echo ".md/.mmd files: $FILE_COUNT" else @@ -2387,7 +2120,7 @@ jobs: fi echo "Diagrams directory ($DIAGRAMS_OUTPUT_PATH):" if [ -d "$DIAGRAMS_OUTPUT_PATH" ]; then - find "$DIAGRAMS_OUTPUT_PATH" -type f \( -name "*.md" -o -name "*.mmd" -o -name ".gitignore" \) | head -20 + find "$DIAGRAMS_OUTPUT_PATH" -type f \( -name "*.md" -o -name "*.mmd" -o -name ".gitignore" \) | sed -n '1,20p' FILE_COUNT=$(find "$DIAGRAMS_OUTPUT_PATH" -type f \( -name "*.md" -o -name "*.mmd" \) | wc -l | tr -d ' ') echo ".md/.mmd files: $FILE_COUNT" else @@ -2404,7 +2137,7 @@ jobs: echo "Added ecosystem.md to $REFERENCE_OUTPUT_PATH/" fi - # Stage all .md, .mmd, and .gitignore files from Stage 2 output directories + # Stage all .md, .mmd, and .gitignore files from Stage 1 output directories # CRITICAL: Use git add on full paths to preserve nested directory structure # This ensures Backend/Authentication/JWT/JWT.md keeps its full path in git @@ -2446,30 +2179,29 @@ jobs: echo "Staged files: $STAGED_COUNT" if [ "$STAGED_COUNT" -gt 0 ]; then - echo "::group::Staged stage 2 files (first 30)" - git diff --cached --name-only | head -30 + echo "::group::Staged stage 1 files (first 30)" + git diff --cached --name-only | sed -n '1,30p' echo "::endgroup::" fi if [ "$STAGED_COUNT" -gt 0 ]; then # Commit and push - git commit -m "docs: Stage 2 - Architecture analysis ($STAGE2_FILES files) [skip ci]" + git commit -m "docs: Stage 1 - Architecture analysis ($STAGE1_FILES files) [skip ci]" git push origin "$BRANCH_NAME" - echo "Committed and pushed $STAGED_COUNT stage 2 files" + echo "Committed and pushed $STAGED_COUNT stage 1 files" set_output "committed" "true" else - echo "::error title=Stage 2 output not staged::Stage 2 reported $STAGE2_FILES files but none were staged; the files were generated outside the reference and diagrams directories, or not at all." + echo "::error title=Stage 1 output not staged::Stage 1 reported $STAGE1_FILES files but none were staged; the files were generated outside the reference and diagrams directories, or not at all." set_output "committed" "false" fi - - name: Update pull request with stage 2 progress - if: always() && steps.commit-stage2.outputs.committed == 'true' && steps.create-initial-pr.outputs.pull-request-number + - name: Update pull request with stage 1 progress + if: always() && steps.commit-stage1.outputs.committed == 'true' && steps.create-initial-pr.outputs.pull-request-number env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} RUN_ID_VAR: ${{ env.RUN_ID }} REPO_NAME: ${{ github.repository }} run: | @@ -2484,44 +2216,41 @@ jobs: This PR is being updated as each documentation stage completes. ### Progress - - โœ… Stage 1: Inline Documentation - Completed ($STAGE1_FILES files) - - โœ… Stage 2: Architecture Analysis - Completed ($STAGE2_FILES files) - - โณ Stage 3: Tutorial Generation - Running... - - โฑ๏ธ Stage 4: Repository Documentation - Pending + - โœ… Stage 1: Architecture Analysis - Completed ($STAGE1_FILES files) + - โณ Stage 2: Tutorial Generation - Running... + - โฑ๏ธ Stage 3: Repository Documentation - Pending --- ๐Ÿฆฉ Generated by [Flamingo Code Documentation](https://flamingo.run)" gh pr edit "$PR_NUMBER" --body "$PR_BODY" - echo "Pull request #$PR_NUMBER updated with stage 2 progress" + echo "Pull request #$PR_NUMBER updated with stage 1 progress" - - name: Report stage 2 progress + - name: Report stage 1 progress if: always() && contains(env.STAGES, 'codewiki') && env.HUB_BASE_URL != '' continue-on-error: true env: WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || 'skipped' }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - # Use outputs from either CodeWiki (stage2) or Claude alternative (stage2_alt) - STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || steps.stage2_alt.outputs.stage2_status || 'skipped' }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} + # Use outputs from either CodeWiki (stage1) or Claude alternative (stage1_alt) + STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || steps.stage1_alt.outputs.stage1_status || 'skipped' }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} PR_URL: ${{ steps.create-initial-pr.outputs.pull-request-url }} PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} run: | source /tmp/workflow-helpers.sh CALLBACK_URL="${HUB_BASE_URL}/api/code-documentation/webhook" - echo "Reporting stage 2 (reference docs): $STAGE2_STATUS, $STAGE2_FILES files" + echo "Reporting stage 1 (reference docs): $STAGE1_STATUS, $STAGE1_FILES files" report_stage_progress "$CALLBACK_URL" "$WEBHOOK_SECRET" "$RUN_ID" "$REPO_ID" \ "$WORKFLOW_RUN_ID" "$WORKFLOW_URL" "tutorials" \ - "$STAGE1_STATUS" "$STAGE1_FILES" "$STAGE2_STATUS" "$STAGE2_FILES" "" "0" "" "0" \ + "$STAGE1_STATUS" "$STAGE1_FILES" "" "0" "" "0" \ "$PR_URL" "$PR_NUMBER" # ========================================================================= - # STAGE 3: TUTORIALS + # STAGE 2: TUTORIALS # Getting-started guides and how-to tutorials, written by the Code # Documentation lane (code-documentation-lib.mjs): one hub call per # tutorial with the forced `emit_document` tool and, once the repository's @@ -2531,19 +2260,21 @@ jobs: # dev/getting-started-dev, dev/architecture-overview-dev # ========================================================================= - name: Install tutorial and repository docs dependencies - # Stage 3 AND Stage 4 (generate-repo-docs.cjs) share this tree. Gated - # on either: a repository configured with inline-docs + repo-docs and no - # tutorials reached Stage 4 with no dependencies at all, run 35302244804. + # Stage 2 AND Stage 3 (generate-repo-docs.cjs) share this tree. Gated + # on either: a repository configured with repo-docs and no tutorials + # reached Stage 3 with no dependencies at all, run 35302244804. if: contains(env.STAGES, 'tutorials') || contains(env.STAGES, 'repo-docs') - # Isolated deps tree (see Stage 1) - no reconciliation with the target repo. + # Installed in an ISOLATED tree under RUNNER_TEMP, not the target repo: npm + # resolves against an empty package.json here, so a target repo's own peer + # conflicts can never make this fail. Generators find these via NODE_PATH. # No model SDK: both stages call Claude through the hub. run: | mkdir -p "$RUNNER_TEMP/doc-orch-deps" && cd "$RUNNER_TEMP/doc-orch-deps" npm init -y >/dev/null 2>&1 npm install zod@3.25.76 glob@13.0.6 - - name: Generate tutorials (stage 3) - id: stage3 + - name: Generate tutorials (stage 2) + id: stage2 if: contains(env.STAGES, 'tutorials') env: # No ANTHROPIC_API_KEY โ€” this stage calls Claude through the hub @@ -2551,17 +2282,17 @@ jobs: WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} # Pass through output paths from workflow env (OSS Tenant Structure) DOCS_OUTPUT_PATH: ${{ env.DOCS_OUTPUT_PATH }} - # Stage 2 outputs (for context) + # Stage 1 outputs (for context) REFERENCE_OUTPUT_PATH: ${{ env.REFERENCE_OUTPUT_PATH }} DIAGRAMS_OUTPUT_PATH: ${{ env.DIAGRAMS_OUTPUT_PATH }} - # Stage 3 outputs + # Stage 2 outputs GETTING_STARTED_OUTPUT_PATH: ${{ env.GETTING_STARTED_OUTPUT_PATH }} DEVELOPMENT_OUTPUT_PATH: ${{ env.DEVELOPMENT_OUTPUT_PATH }} # Claude model SSOT โ€” see workflow env CLAUDE_MODEL block CLAUDE_MODEL: ${{ env.CLAUDE_MODEL }} # Stage timeout - STAGE3_TIMEOUT_HOURS: ${{ env.STAGE3_TIMEOUT_HOURS }} - # Unified file discovery result (same files as Stage 1 and 2) + STAGE2_TIMEOUT_HOURS: ${{ env.STAGE2_TIMEOUT_HOURS }} + # Unified file discovery result (same files as Stage 1) SOURCE_FILES_LIST: ${{ steps.discover_files.outputs.source_files_list }} # NODE_PATH to find modules from /tmp/ scripts NODE_PATH: ${{ runner.temp }}/doc-orch-deps/node_modules @@ -2573,9 +2304,9 @@ jobs: VALIDATION_RULES_PATH: ${{ env.VALIDATION_RULES_PATH }} # Flamingo Markdown Guidelines (optional) GUIDELINES_PATH: ${{ env.GUIDELINES_PATH }} - # Stage 3 tracking files (configurable paths) - STAGE3_FILES_TRACKER: ${{ env.STAGE3_FILES_TRACKER }} - STAGE3_STATS_FILE: ${{ env.STAGE3_STATS_FILE }} + # Stage 2 tracking files (configurable paths) + STAGE2_FILES_TRACKER: ${{ env.STAGE2_FILES_TRACKER }} + STAGE2_STATS_FILE: ${{ env.STAGE2_STATS_FILE }} # Custom AI Instructions (All Stages) CUSTOM_REPO_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }} # Analysis Exclusions @@ -2586,26 +2317,29 @@ jobs: source /tmp/workflow-helpers.sh # Script already downloaded to /tmp/ in setup step. run_stage records - # the outcome as stage3_status โ€” see its note in workflow-helpers.sh. - run_stage "Stage 3" "$STAGE3_TIMEOUT_HOURS" stage3_status node /tmp/generate-tutorials-voltagent.cjs + # the outcome as stage2_status โ€” see its note in workflow-helpers.sh. + run_stage "Stage 2" "$STAGE2_TIMEOUT_HOURS" stage2_status node /tmp/generate-tutorials-voltagent.cjs # Count files from both OSS Tenant Structure directories GETTING_STARTED_FILES=$(count_markdown_files "${GETTING_STARTED_OUTPUT_PATH}") DEVELOPMENT_FILES=$(count_markdown_files "${DEVELOPMENT_OUTPUT_PATH}") TUTORIAL_FILES=$((GETTING_STARTED_FILES + DEVELOPMENT_FILES)) echo "Tutorials written: $TUTORIAL_FILES ($GETTING_STARTED_FILES getting started, $DEVELOPMENT_FILES development)" - set_output "stage3_files" "$TUTORIAL_FILES" + set_output "stage2_files" "$TUTORIAL_FILES" # ========================================================================= - # COMMIT STAGE 3 RESULTS (progressive pull request) + # COMMIT STAGE 2 RESULTS (progressive pull request) # ========================================================================= - # `!= ''` โ€” the run_stage commit rule, stated once at Stage 1. - - name: Commit and push stage 3 results - if: always() && steps.stage3.outputs.stage3_status != '' - id: commit-stage3 + # `!= ''`, not `== 'completed'`: runs on a FAILED stage too โ€” see run_stage + # in workflow-helpers.sh (partial output is worth committing; the status + # is what reports the truth home). The build gate holds every run_stage + # commit step to this predicate. + - name: Commit and push stage 2 results + if: always() && steps.stage2.outputs.stage2_status != '' + id: commit-stage2 env: BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} - STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files }} GETTING_STARTED_OUTPUT_PATH: ${{ env.GETTING_STARTED_OUTPUT_PATH }} DEVELOPMENT_OUTPUT_PATH: ${{ env.DEVELOPMENT_OUTPUT_PATH }} run: | @@ -2627,7 +2361,7 @@ jobs: fi done - # Stage all .md and .gitignore files from Stage 3 output directories + # Stage all .md and .gitignore files from Stage 2 output directories find "$GETTING_STARTED_OUTPUT_PATH" "$DEVELOPMENT_OUTPUT_PATH" -type f \( -name "*.md" -o -name ".gitignore" \) \ -exec git add -f {} \; 2>/dev/null || true @@ -2636,24 +2370,23 @@ jobs: if [ "$STAGED_COUNT" -gt 0 ]; then # Commit and push - git commit -m "docs: Stage 3 - Tutorial generation ($STAGE3_FILES files) [skip ci]" + git commit -m "docs: Stage 2 - Tutorial generation ($STAGE2_FILES files) [skip ci]" git push origin "$BRANCH_NAME" - echo "Committed and pushed $STAGED_COUNT stage 3 files" + echo "Committed and pushed $STAGED_COUNT stage 2 files" set_output "committed" "true" else - echo "::warning title=Stage 3 produced nothing::No tutorials to commit." + echo "::warning title=Stage 2 produced nothing::No tutorials to commit." set_output "committed" "false" fi - - name: Update pull request with stage 3 progress - if: always() && steps.commit-stage3.outputs.committed == 'true' && steps.create-initial-pr.outputs.pull-request-number + - name: Update pull request with stage 2 progress + if: always() && steps.commit-stage2.outputs.committed == 'true' && steps.create-initial-pr.outputs.pull-request-number env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} - STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files || 0 }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || 0 }} RUN_ID_VAR: ${{ env.RUN_ID }} REPO_NAME: ${{ github.repository }} run: | @@ -2668,51 +2401,48 @@ jobs: This PR is being updated as each documentation stage completes. ### Progress - - โœ… Stage 1: Inline Documentation - Completed ($STAGE1_FILES files) - - โœ… Stage 2: Architecture Analysis - Completed ($STAGE2_FILES files) - - โœ… Stage 3: Tutorial Generation - Completed ($STAGE3_FILES files) - - โณ Stage 4: Repository Documentation - Running... + - โœ… Stage 1: Architecture Analysis - Completed ($STAGE1_FILES files) + - โœ… Stage 2: Tutorial Generation - Completed ($STAGE2_FILES files) + - โณ Stage 3: Repository Documentation - Running... --- ๐Ÿฆฉ Generated by [Flamingo Code Documentation](https://flamingo.run)" gh pr edit "$PR_NUMBER" --body "$PR_BODY" - echo "Pull request #$PR_NUMBER updated with stage 3 progress" + echo "Pull request #$PR_NUMBER updated with stage 2 progress" - - name: Report stage 3 progress + - name: Report stage 2 progress if: always() && contains(env.STAGES, 'tutorials') && env.HUB_BASE_URL != '' continue-on-error: true env: WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || 'skipped' }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || steps.stage2_alt.outputs.stage2_status || 'skipped' }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} - STAGE3_STATUS: ${{ steps.stage3.outputs.stage3_status || 'skipped' }} - STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files || 0 }} + STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || steps.stage1_alt.outputs.stage1_status || 'skipped' }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} + STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || 'skipped' }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || 0 }} PR_URL: ${{ steps.create-initial-pr.outputs.pull-request-url }} PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} run: | source /tmp/workflow-helpers.sh CALLBACK_URL="${HUB_BASE_URL}/api/code-documentation/webhook" - echo "Reporting stage 3 (tutorials): $STAGE3_STATUS, $STAGE3_FILES files" + echo "Reporting stage 2 (tutorials): $STAGE2_STATUS, $STAGE2_FILES files" report_stage_progress "$CALLBACK_URL" "$WEBHOOK_SECRET" "$RUN_ID" "$REPO_ID" \ "$WORKFLOW_RUN_ID" "$WORKFLOW_URL" "repo-docs" \ - "$STAGE1_STATUS" "$STAGE1_FILES" "$STAGE2_STATUS" "$STAGE2_FILES" "$STAGE3_STATUS" "$STAGE3_FILES" "" "0" \ + "$STAGE1_STATUS" "$STAGE1_FILES" "$STAGE2_STATUS" "$STAGE2_FILES" "" "0" \ "$PR_URL" "$PR_NUMBER" # ========================================================================= - # STAGE 4: REPOSITORY DOCS + # STAGE 3: REPOSITORY DOCS # Copies the legal files the hub's list names from the template repo # Generates/updates README.md, CONTRIBUTING.md and the docs index through # the Code Documentation lane (one hub call per document, no model key) # ========================================================================= - - name: Generate repository docs (stage 4) - id: stage4 + - name: Generate repository docs (stage 3) + id: stage3 if: contains(env.STAGES, 'repo-docs') env: # No ANTHROPIC_API_KEY โ€” this stage calls Claude through the hub. @@ -2727,7 +2457,7 @@ jobs: DEVELOPMENT_OUTPUT_PATH: ${{ env.DEVELOPMENT_OUTPUT_PATH }} # Claude model SSOT โ€” see workflow env CLAUDE_MODEL block CLAUDE_MODEL: ${{ env.CLAUDE_MODEL }} - STAGE4_TIMEOUT_HOURS: ${{ env.STAGE4_TIMEOUT_HOURS }} + STAGE3_TIMEOUT_HOURS: ${{ env.STAGE3_TIMEOUT_HOURS }} # NODE_PATH to find modules from /tmp/ scripts NODE_PATH: ${{ runner.temp }}/doc-orch-deps/node_modules # YouTube Integration - SECURITY: API key from secrets, NOT dispatch payload @@ -2738,10 +2468,10 @@ jobs: VALIDATION_RULES_PATH: ${{ env.VALIDATION_RULES_PATH }} # Flamingo Markdown Guidelines (optional) GUIDELINES_PATH: ${{ env.GUIDELINES_PATH }} - # Stage 4 tracking files (configurable paths) - STAGE4_FILES_TRACKER: ${{ env.STAGE4_FILES_TRACKER }} + # Stage 3 tracking files (configurable paths) + STAGE3_FILES_TRACKER: ${{ env.STAGE3_FILES_TRACKER }} # Where the script records the legal files it copied (read by the checks and the commit) - STAGE4_LEGAL_FILES: ${{ env.LEGAL_FILES_LIST }} + STAGE3_LEGAL_FILES: ${{ env.LEGAL_FILES_LIST }} # Custom AI Instructions (All Stages) CUSTOM_REPO_INSTRUCTIONS: ${{ env.CUSTOM_INSTRUCTIONS }} # Analysis Exclusions @@ -2773,11 +2503,11 @@ jobs: # 3. README, CONTRIBUTING and the docs index, one hub call each. # Script already downloaded to /tmp/ in "Download pipeline scripts" - # run_stage records the outcome as stage4_status โ€” see its note in + # run_stage records the outcome as stage3_status โ€” see its note in # workflow-helpers.sh. The file count below is REPORTING, not a # status: inferring "completed" from it meant a crashed run that left # a previous commit's README standing reported success. - run_stage "Stage 4" "$STAGE4_TIMEOUT_HOURS" stage4_status node /tmp/generate-repo-docs.cjs + run_stage "Stage 3" "$STAGE3_TIMEOUT_HOURS" stage3_status node /tmp/generate-repo-docs.cjs # 4. Count results REPO_DOCS=0 @@ -2789,27 +2519,27 @@ jobs: fi done - set_output "stage4_files" "$REPO_DOCS" + set_output "stage3_files" "$REPO_DOCS" if [ "$REPO_DOCS" -gt 0 ]; then echo "Repository docs present: $REPO_DOCS" else - echo "::warning title=Stage 4 produced nothing::No repository docs (README.md, CONTRIBUTING.md, legal files) are present." + echo "::warning title=Stage 3 produced nothing::No repository docs (README.md, CONTRIBUTING.md, legal files) are present." fi # ========================================================================= - # COMMIT STAGE 4 RESULTS (progressive pull request) + # COMMIT STAGE 3 RESULTS (progressive pull request) # ========================================================================= - # `!= ''` โ€” the run_stage commit rule, stated once at Stage 1. - - name: Commit and push stage 4 results - if: always() && steps.stage4.outputs.stage4_status != '' - id: commit-stage4 + # `!= ''` โ€” the run_stage commit rule, stated once at Stage 2. + - name: Commit and push stage 3 results + if: always() && steps.stage3.outputs.stage3_status != '' + id: commit-stage3 env: BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} - STAGE4_FILES: ${{ steps.stage4.outputs.stage4_files }} + STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files }} run: | source /tmp/workflow-helpers.sh - # .gitignore in each stage 4 managed directory + # .gitignore in each stage 3 managed directory for managed_dir in docs/api docs/deployment docs/operations docs/cli; do if [ -d "$managed_dir" ]; then { @@ -2826,14 +2556,14 @@ jobs: done # Stage repository documentation files - # AGENTS.md (and CLAUDE.md's @AGENTS.md import) are here as the backstop for a run whose Stage 2 commit did not happen. + # AGENTS.md (and CLAUDE.md's @AGENTS.md import) are here as the backstop for a run whose Stage 1 commit did not happen. for f in README.md CONTRIBUTING.md $(cat "$LEGAL_FILES_LIST" 2>/dev/null) AGENTS.md CLAUDE.md; do if [ -f "$f" ]; then git add -f "$f" fi done - # Stage Stage 4 managed directories + # Stage Stage 3 managed directories for managed_dir in docs/api docs/deployment docs/operations docs/cli; do if [ -d "$managed_dir" ]; then git add -f "$managed_dir/" 2>/dev/null || true @@ -2850,25 +2580,88 @@ jobs: if [ "$STAGED_COUNT" -gt 0 ]; then # Commit and push - git commit -m "docs: Stage 4 - Repository documentation ($STAGE4_FILES files) [skip ci]" + git commit -m "docs: Stage 3 - Repository documentation ($STAGE3_FILES files) [skip ci]" git push origin "$BRANCH_NAME" - echo "Committed and pushed $STAGED_COUNT stage 4 files" + echo "Committed and pushed $STAGED_COUNT stage 3 files" set_output "committed" "true" else - echo "No stage 4 changes to commit" + echo "No stage 3 changes to commit" set_output "committed" "false" fi - - name: Update pull request with stage 4 progress - if: always() && steps.commit-stage4.outputs.committed == 'true' && steps.create-initial-pr.outputs.pull-request-number + # ========================================================================= + # RESTORE WHAT STAGES 2 AND 3 DID NOT REGENERATE + # The trees the docs removal deleted for Stages 2 and 3, judged on how those stages + # actually ended: a tree whose stage completed stays as that stage wrote it (a page it + # refused is gone, never the base's old copy); one whose stage did not complete comes + # back from the base. A FULL wipe (the docs root) that some stage did not complete + # gets back only the files that are missing now, so nothing a stage wrote is + # overwritten. + # ========================================================================= + - name: Restore docs stages 2 and 3 did not regenerate + if: always() + env: + BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} + STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status }} + STAGE1_ALT_STATUS: ${{ steps.stage1_alt.outputs.stage1_status }} + STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status }} + STAGE3_STATUS: ${{ steps.stage3.outputs.stage3_status }} + run: | + if [ -z "$CLEAN_TARGETS_RECORD" ]; then + echo "Nothing was removed; nothing to restore" + exit 0 + fi + STAGE1_OK=false + [ "$STAGE1_STATUS" = "completed" ] && STAGE1_OK=true + [ "$STAGE1_ALT_STATUS" = "completed" ] && STAGE1_OK=true + + RESTORED=0 + while IFS= read -r target; do + [ -n "$target" ] || continue + case "$target" in + "$GETTING_STARTED_OUTPUT_PATH"|"$DEVELOPMENT_OUTPUT_PATH") + [ "$STAGE2_STATUS" = "completed" ] && continue + if git checkout "$PRE_CLEAN_SHA" -- "$target" 2>/dev/null; then + echo "::notice title=Docs restored::$target restored from $PRE_CLEAN_SHA; Stage 2 did not complete." + RESTORED=1 + fi ;; + "$DOCS_OUTPUT_PATH") + if [ "$STAGE1_OK" = true ] && [ "$STAGE2_STATUS" = "completed" ] && \ + [ "$STAGE3_STATUS" = "completed" ]; then + continue + fi + # Only what is missing now: the stages that did complete keep what they wrote. + MISSING=/tmp/docs-missing-after-stages.txt + : > "$MISSING" + while IFS= read -r -d '' f; do + [ -e "$f" ] || printf '%s\n' "$f" >> "$MISSING" + done < <(git ls-tree -r -z --name-only "$PRE_CLEAN_SHA" -- "$target") + if [ -s "$MISSING" ]; then + GIT_LITERAL_PATHSPECS=1 git checkout "$PRE_CLEAN_SHA" --pathspec-from-file="$MISSING" + echo "::notice title=Docs restored::$(wc -l < "$MISSING" | tr -d ' ') file(s) under $target restored from $PRE_CLEAN_SHA; a stage did not complete." + RESTORED=1 + fi ;; + esac + done <<< "$CLEAN_TARGETS_RECORD" + + # `git checkout -- ` stages exactly those paths; no `git add -A`. + if [ "$RESTORED" -eq 1 ] && [ -n "$(git diff --cached --name-only)" ]; then + git commit -m "chore(docs): restore documentation stages 2 and 3 did not regenerate [skip ci]" + git push origin "$BRANCH_NAME" + echo "Restore committed and pushed" + else + echo "Nothing to restore" + fi + + - name: Update pull request with stage 3 progress + if: always() && steps.commit-stage3.outputs.committed == 'true' && steps.create-initial-pr.outputs.pull-request-number env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || 0 }} STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files || 0 }} - STAGE4_FILES: ${{ steps.stage4.outputs.stage4_files || 0 }} RUN_ID_VAR: ${{ env.RUN_ID }} REPO_NAME: ${{ github.repository }} run: | @@ -2883,17 +2676,16 @@ jobs: This PR is being updated as each documentation stage completes. ### Progress - - โœ… Stage 1: Inline Documentation - Completed ($STAGE1_FILES files) - - โœ… Stage 2: Architecture Analysis - Completed ($STAGE2_FILES files) - - โœ… Stage 3: Tutorial Generation - Completed ($STAGE3_FILES files) - - โœ… Stage 4: Repository Documentation - Completed ($STAGE4_FILES files) + - โœ… Stage 1: Architecture Analysis - Completed ($STAGE1_FILES files) + - โœ… Stage 2: Tutorial Generation - Completed ($STAGE2_FILES files) + - โœ… Stage 3: Repository Documentation - Completed ($STAGE3_FILES files) --- ๐Ÿฆฉ Generated by [Flamingo Code Documentation](https://flamingo.run)" gh pr edit "$PR_NUMBER" --body "$PR_BODY" - echo "Pull request #$PR_NUMBER updated with stage 4 progress" + echo "Pull request #$PR_NUMBER updated with stage 3 progress" # ========================================================================= # VALIDATE GENERATED MARKDOWN @@ -2926,32 +2718,30 @@ jobs: node /tmp/validate-markdown.js "$DEVELOPMENT_OUTPUT_PATH" 2>&1 || true echo "::endgroup::" - - name: Report stage 4 progress + - name: Report stage 3 progress if: always() && env.HUB_BASE_URL != '' continue-on-error: true env: WEBHOOK_SECRET: ${{ secrets.FLAMINGO_HUB_SECRET }} WORKFLOW_RUN_ID: ${{ github.run_id }} WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || 'skipped' }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || steps.stage2_alt.outputs.stage2_status || 'skipped' }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} + STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || steps.stage1_alt.outputs.stage1_status || 'skipped' }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} + STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || 'skipped' }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || 0 }} STAGE3_STATUS: ${{ steps.stage3.outputs.stage3_status || 'skipped' }} STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files || 0 }} - STAGE4_STATUS: ${{ steps.stage4.outputs.stage4_status || 'skipped' }} - STAGE4_FILES: ${{ steps.stage4.outputs.stage4_files || 0 }} PR_URL: ${{ steps.create-initial-pr.outputs.pull-request-url }} PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} run: | source /tmp/workflow-helpers.sh CALLBACK_URL="${HUB_BASE_URL}/api/code-documentation/webhook" - echo "Reporting stage 4 (repository docs): $STAGE4_STATUS, $STAGE4_FILES files" + echo "Reporting stage 3 (repository docs): $STAGE3_STATUS, $STAGE3_FILES files" report_stage_progress "$CALLBACK_URL" "$WEBHOOK_SECRET" "$RUN_ID" "$REPO_ID" \ "$WORKFLOW_RUN_ID" "$WORKFLOW_URL" "creating-pr" \ - "$STAGE1_STATUS" "$STAGE1_FILES" "$STAGE2_STATUS" "$STAGE2_FILES" "$STAGE3_STATUS" "$STAGE3_FILES" \ - "$STAGE4_STATUS" "$STAGE4_FILES" "$PR_URL" "$PR_NUMBER" + "$STAGE1_STATUS" "$STAGE1_FILES" "$STAGE2_STATUS" "$STAGE2_FILES" \ + "$STAGE3_STATUS" "$STAGE3_FILES" "$PR_URL" "$PR_NUMBER" # ========================================================================= # FINALIZE THE PULL REQUEST @@ -2961,8 +2751,7 @@ jobs: source /tmp/workflow-helpers.sh # Remove stats files - cleanup_path ".doc-stage1-stats.json" - cleanup_path ".doc-stage3-stats.json" + cleanup_path ".doc-stage2-stats.json" # Remove run status file (created for the initial PR). # Legacy name kept so branches started before the rename still clean up. @@ -2983,19 +2772,17 @@ jobs: run: | source /tmp/workflow-helpers.sh echo "Docs root: $DOCS_OUTPUT_PATH" - echo "Stage 2: $REFERENCE_OUTPUT_PATH (reference), $DIAGRAMS_OUTPUT_PATH (diagrams)" - echo "Stage 3: $GETTING_STARTED_OUTPUT_PATH (getting started), $DEVELOPMENT_OUTPUT_PATH (development)" + echo "Stage 1: $REFERENCE_OUTPUT_PATH (reference), $DIAGRAMS_OUTPUT_PATH (diagrams)" + echo "Stage 2: $GETTING_STARTED_OUTPUT_PATH (getting started), $DEVELOPMENT_OUTPUT_PATH (development)" # Count untracked/modified files before staging BEFORE_COUNT=$(git status --porcelain | wc -l) echo "Changed files in the working tree: $BEFORE_COUNT" - # Stage ALL .md and .mmd files anywhere in the repo (for inline docs generated next to source files) - # This catches Stage 1 inline docs (hidden: .FileName.md), Stage 2 reference/diagrams, Stage 3 tutorials, and Stage 4 repo docs - # Find all .md and .mmd files recursively, including hidden files (.*.md) - # Includes README.md, CONTRIBUTING.md and the legal files from Stage 4 - # Includes .mmd Mermaid diagram files from Stage 2 (CodeWiki/Claude architecture) - find . \( -name "*.md" -o -name ".*.md" -o -name "*.mmd" \) -type f \ + # Stage ALL .md and .mmd files anywhere in the repo: Stage 1 reference docs and + # diagrams (.mmd Mermaid files), Stage 2 tutorials, and Stage 3's README.md, + # CONTRIBUTING.md and legal files. + find . \( -name "*.md" -o -name "*.mmd" \) -type f \ -not -path "./node_modules/*" \ -not -path "./.git/*" \ -not -name "CHANGELOG.md" \ @@ -3005,7 +2792,7 @@ jobs: find . \( -name "*.md" -o -name ".*.md" -o -name "*.mmd" \) -type f \ -not -path "./node_modules/*" \ -not -path "./.git/*" \ - -not -name "CHANGELOG.md" | head -100 + -not -name "CHANGELOG.md" | sed -n '1,100p' echo "::endgroup::" # Count staged files @@ -3014,7 +2801,7 @@ jobs: set_output "staged_count" "$STAGED_COUNT" echo "::group::Staged files (first 50)" - git diff --cached --name-only | head -50 + git diff --cached --name-only | sed -n '1,50p' echo "::endgroup::" # "Changes" means the BRANCH differs from the documented source head, not @@ -3035,14 +2822,12 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ steps.create-initial-pr.outputs.pull-request-number }} - STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || 'skipped' }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || steps.stage2_alt.outputs.stage2_status || 'skipped' }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} + STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || steps.stage1_alt.outputs.stage1_status || 'skipped' }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} + STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || 'skipped' }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || 0 }} STAGE3_STATUS: ${{ steps.stage3.outputs.stage3_status || 'skipped' }} STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files || 0 }} - STAGE4_STATUS: ${{ steps.stage4.outputs.stage4_status || 'skipped' }} - STAGE4_FILES: ${{ steps.stage4.outputs.stage4_files || 0 }} RUN_ID_VAR: ${{ env.RUN_ID }} REPO_NAME: ${{ github.repository }} run: | @@ -3059,30 +2844,24 @@ jobs: **Run ID:** \`$RUN_ID_VAR\` - ### Stage 1: Inline Documentation + ### Stage 1: Architecture Analysis - Status: $STAGE1_STATUS - Files generated: $STAGE1_FILES - - Generated .md files next to source classes explaining their purpose + - Architecture overview and module documentation - ### Stage 2: Architecture Analysis + ### Stage 2: AI Tutorial Generator - Status: $STAGE2_STATUS - Files generated: $STAGE2_FILES - - Architecture overview and module documentation + - Getting started guides and how-to tutorials - ### Stage 3: AI Tutorial Generator + ### Stage 3: Repository Documentation - Status: $STAGE3_STATUS - Files generated: $STAGE3_FILES - - Getting started guides and how-to tutorials - - ### Stage 4: Repository Documentation - - Status: $STAGE4_STATUS - - Files generated: $STAGE4_FILES - README.md, CONTRIBUTING.md and the legal files (license, terms, privacy and security policies) --- **Review checklist:** - - [ ] Check generated inline docs for accuracy - [ ] Review architecture documentation - [ ] Test code examples in tutorials - [ ] Review README.md and CONTRIBUTING.md updates @@ -3113,17 +2892,15 @@ jobs: # Single source of truth for the branch name (was rebuilt by hand below, # which silently drifted from "Create docs branch" on every rename) BRANCH_NAME: ${{ steps.create-pr-branch.outputs.branch_name }} - STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || 'skipped' }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || 0 }} - # Stage 2: Check both CodeWiki and Claude alternative, mark as failed if step failed - STAGE2_STATUS: ${{ steps.stage2.outcome == 'failure' && 'failed' || steps.stage2_alt.outcome == 'failure' && 'failed' || steps.stage2.outputs.stage2_status || steps.stage2_alt.outputs.stage2_status || 'skipped' }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || 0 }} + # Stage 1: Check both CodeWiki and Claude alternative, mark as failed if step failed + STAGE1_STATUS: ${{ steps.stage1.outcome == 'failure' && 'failed' || steps.stage1_alt.outcome == 'failure' && 'failed' || steps.stage1.outputs.stage1_status || steps.stage1_alt.outputs.stage1_status || 'skipped' }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || 0 }} + STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || 'skipped' }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || 0 }} STAGE3_STATUS: ${{ steps.stage3.outputs.stage3_status || 'skipped' }} STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files || 0 }} - STAGE4_STATUS: ${{ steps.stage4.outputs.stage4_status || 'skipped' }} - STAGE4_FILES: ${{ steps.stage4.outputs.stage4_files || 0 }} # Track if critical steps failed (continue-on-error: false steps) - STAGE2_OUTCOME: ${{ steps.stage2.outcome || 'skipped' }} + STAGE1_OUTCOME: ${{ steps.stage1.outcome || 'skipped' }} CODEWIKI_INSTALL_OUTCOME: ${{ steps.codewiki_install.outcome || 'skipped' }} CODEWIKI_CONFIG_OUTCOME: ${{ steps.codewiki_config.outcome || 'skipped' }} run: | @@ -3150,7 +2927,7 @@ jobs: echo "::group::Inputs to the final status" echo "JOB_STATUS=$JOB_STATUS" echo "HAS_CHANGES=$HAS_CHANGES" - echo "STAGE2_OUTCOME=$STAGE2_OUTCOME" + echo "STAGE1_OUTCOME=$STAGE1_OUTCOME" echo "CODEWIKI_INSTALL_OUTCOME=$CODEWIKI_INSTALL_OUTCOME" echo "CODEWIKI_CONFIG_OUTCOME=$CODEWIKI_CONFIG_OUTCOME" echo "::endgroup::" @@ -3163,8 +2940,8 @@ jobs: if [ "$JOB_STATUS" = "cancelled" ]; then STATUS="cancelled" REASON="the workflow run was cancelled" - # Check if critical stage 2 (CodeWiki) failed - this has continue-on-error: false - elif [ "$STAGE2_OUTCOME" = "failure" ]; then + # Check if critical stage 1 (CodeWiki) failed - this has continue-on-error: false + elif [ "$STAGE1_OUTCOME" = "failure" ]; then STATUS="failure" REASON="the CodeWiki stage failed" # Check if CodeWiki installation failed @@ -3180,9 +2957,9 @@ jobs: STATUS="failure" REASON="the job status is $JOB_STATUS" # A requested stage that FAILED fails the run, whatever the others wrote: - # a run whose stages 3 and 4 wrote nothing used to report success because - # stage 2 had committed reference docs. - elif FAILED_STAGES=$(for n in 1 2 3 4; do v="STAGE${n}_STATUS"; [ "${!v}" = "failed" ] && printf 'stage %s, ' "$n"; done) && [ -n "$FAILED_STAGES" ]; then + # a run whose stages 2 and 3 wrote nothing used to report success because + # stage 1 had committed reference docs. + elif FAILED_STAGES=$(for n in 2 3 4; do v="STAGE${n}_STATUS"; [ "${!v}" = "failed" ] && printf 'stage %s, ' "$n"; done) && [ -n "$FAILED_STAGES" ]; then STATUS="failure" REASON="${FAILED_STAGES%, } failed" # Check if we have any documentation changes @@ -3215,8 +2992,8 @@ jobs: # Send final webhook using helper function report_final_status "$CALLBACK_URL" "$WEBHOOK_SECRET" "$RUN_ID" "$REPO_ID" \ "$WORKFLOW_RUN_ID" "$WORKFLOW_URL" "$STATUS" "$PR_URL" "$PR_NUMBER" "$SAFE_BRANCH" \ - "$STAGE1_STATUS" "$STAGE1_FILES" "$STAGE2_STATUS" "$STAGE2_FILES" "$STAGE3_STATUS" "$STAGE3_FILES" \ - "$STAGE4_STATUS" "$STAGE4_FILES" + "$STAGE1_STATUS" "$STAGE1_FILES" "$STAGE2_STATUS" "$STAGE2_FILES" \ + "$STAGE3_STATUS" "$STAGE3_FILES" # Final cleanup: remove workflow helpers file cleanup_path "/tmp/workflow-helpers.sh" @@ -3226,15 +3003,13 @@ jobs: - name: Write job summary if: always() env: - STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || 'skipped' }} - STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || '0' }} - STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || steps.stage2_alt.outputs.stage2_status || 'skipped' }} - STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || steps.stage2_alt.outputs.stage2_files || '0' }} - STAGE2_ENGINE: ${{ steps.detect_language.outputs.codewiki_supported == 'true' && 'CodeWiki' || steps.detect_language.outputs.codewiki_supported == 'false' && 'Claude' || 'engine not chosen' }} + STAGE1_STATUS: ${{ steps.stage1.outputs.stage1_status || steps.stage1_alt.outputs.stage1_status || 'skipped' }} + STAGE1_FILES: ${{ steps.stage1.outputs.stage1_files || steps.stage1_alt.outputs.stage1_files || '0' }} + STAGE1_ENGINE: ${{ steps.detect_language.outputs.codewiki_supported == 'true' && 'CodeWiki' || steps.detect_language.outputs.codewiki_supported == 'false' && 'Claude' || 'engine not chosen' }} + STAGE2_STATUS: ${{ steps.stage2.outputs.stage2_status || 'skipped' }} + STAGE2_FILES: ${{ steps.stage2.outputs.stage2_files || '0' }} STAGE3_STATUS: ${{ steps.stage3.outputs.stage3_status || 'skipped' }} STAGE3_FILES: ${{ steps.stage3.outputs.stage3_files || '0' }} - STAGE4_STATUS: ${{ steps.stage4.outputs.stage4_status || 'skipped' }} - STAGE4_FILES: ${{ steps.stage4.outputs.stage4_files || '0' }} GRAPH_OUTCOME: ${{ steps.graph.outcome || 'skipped' }} PRIMARY_LANGUAGE: ${{ steps.detect_language.outputs.primary_language }} PR_URL: ${{ steps.create-initial-pr.outputs.pull-request-url }} @@ -3247,10 +3022,9 @@ jobs: echo "| Stage | Status | Files |" echo "|-------|--------|-------|" echo "| 0 ยท Code graph | $GRAPH_OUTCOME | โ€“ |" - echo "| 1 ยท Inline docs | $STAGE1_STATUS | $STAGE1_FILES |" - echo "| 2 ยท Reference docs ($STAGE2_ENGINE) | $STAGE2_STATUS | $STAGE2_FILES |" - echo "| 3 ยท Tutorials | $STAGE3_STATUS | $STAGE3_FILES |" - echo "| 4 ยท Repository docs | $STAGE4_STATUS | $STAGE4_FILES |" + echo "| 2 ยท Reference docs ($STAGE1_ENGINE) | $STAGE1_STATUS | $STAGE1_FILES |" + echo "| 3 ยท Tutorials | $STAGE2_STATUS | $STAGE2_FILES |" + echo "| 4 ยท Repository docs | $STAGE3_STATUS | $STAGE3_FILES |" echo "" if [ -n "$PR_URL" ]; then echo "**Pull request:** $PR_URL"