From 7b9cb5d8b98ba57ad83d45d69f43576f793fc479 Mon Sep 17 00:00:00 2001 From: juliamuiruri4 <40116776+juliamuiruri4@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:20:49 +0300 Subject: [PATCH 1/2] Add staged weekly reporting Fan out read-only quest discovery into validated report artifacts and assemble deterministic parent and child issue previews without enabling schedules or repository writes.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/agentic-refresh/README.md | 27 +- .github/agentic-refresh/governance.md | 17 + .github/agentic-refresh/report-contract.md | 26 + .github/agentic-refresh/tooling.json | 2 + .../workflows/discover-agentic-rag.lock.yml | 458 ++++++- .github/workflows/discover-agentic-rag.md | 43 +- .../discover-context-engineering.lock.yml | 458 ++++++- .../workflows/discover-context-engineering.md | 43 +- .../workflows/discover-foundry-local.lock.yml | 458 ++++++- .github/workflows/discover-foundry-local.md | 43 +- .../discover-foundry-toolkit.lock.yml | 458 ++++++- .github/workflows/discover-foundry-toolkit.md | 43 +- .../discover-microsoft-foundry.lock.yml | 458 ++++++- .../workflows/discover-microsoft-foundry.md | 43 +- .github/workflows/shared/discovery-policy.md | 9 +- .github/workflows/shared/report-output.md | 137 ++ .../workflows/validate-agentic-workflows.yml | 2 +- .github/workflows/validate-content.yml | 6 +- .../workflows/weekly-refresh-digest.lock.yml | 1174 +++++++++++++++++ .github/workflows/weekly-refresh-digest.md | 74 ++ .../workflows/weekly-refresh-orchestrator.yml | 86 ++ scripts/validate-refresh-config.mjs | 225 +++- scripts/weekly-refresh-report.mjs | 1069 +++++++++++++++ 23 files changed, 5044 insertions(+), 315 deletions(-) create mode 100644 .github/workflows/shared/report-output.md create mode 100644 .github/workflows/weekly-refresh-digest.lock.yml create mode 100644 .github/workflows/weekly-refresh-digest.md create mode 100644 .github/workflows/weekly-refresh-orchestrator.yml create mode 100644 scripts/weekly-refresh-report.mjs diff --git a/.github/agentic-refresh/README.md b/.github/agentic-refresh/README.md index 91d9e3b..553295f 100644 --- a/.github/agentic-refresh/README.md +++ b/.github/agentic-refresh/README.md @@ -1,8 +1,10 @@ # Agentic refresh controls This directory contains the governance boundary for quest refresh automation. -Phase 0 established the controls. Phase 1 adds manually dispatched, read-only -discovery workflows without enabling schedules or repository writes. +Phase 0 established the controls. Phase 1 added manually dispatched, read-only +discovery workflows. Phase 2A adds reusable staged reporting, structured +artifacts, and deterministic parent/child previews without enabling schedules or +repository writes. - [`capabilities.json`](./capabilities.json) keeps Buildathon writes disabled until the repository-scoped token and dispatch spikes pass. External @@ -23,22 +25,31 @@ discovery workflows without enabling schedules or repository writes. - The five Quest Master profiles under [`../agents`](../agents) fix each quest's source and path boundary. - The five `discover-*.md` workflows under [`../workflows`](../workflows) run - manual Phase 1 audits. Their shared policy permits only read operations and - returns reports in the agent response. Every automatic issue path is disabled, - and safe-output processing is forced into staged preview mode. + manual or orchestrated audits. Their shared policy permits only read + operations, and `shared/report-output.md` materializes a validated run-scoped + JSON artifact. Every automatic issue path is disabled, and safe-output + processing is forced into staged preview mode. +- `weekly-refresh-orchestrator.yml` fans out to all five reusable compiled + workflows only through manual dispatch. `weekly-refresh-digest.md` downloads + available quest artifacts, converts missing scopes to blocked results, and + uploads deterministic parent and child issue previews. +- `scripts/weekly-refresh-report.mjs` is the machine-readable authority for ISO + week context, schema validation, lifecycle/source/path boundaries, materiality, + markers, finding keys, deduplication keys, and preview rendering. Run the local Phase 0 checks with: ```bash node scripts/validate-refresh-config.mjs +node scripts/weekly-refresh-report.mjs self-test node scripts/validate-content-links.mjs mkdocs build --strict gh aw compile --strict --validate ``` -No scheduled or write-capable agentic workflow is enabled. Phase 1 workflows -cannot be live-trialed through `workflow_dispatch` until their compiled -definitions are available on the default branch. +No scheduled or write-capable agentic workflow is enabled. The discovery and +reporting workflows cannot be live-trialed through `workflow_dispatch` until +their compiled definitions are available on the default branch. gh-aw v0.77.5 normally reserves `issues: write` for generated failure-reporting jobs even when issue handlers are disabled. Phase 1 additionally sets diff --git a/.github/agentic-refresh/governance.md b/.github/agentic-refresh/governance.md index 19d235d..6af971d1 100644 --- a/.github/agentic-refresh/governance.md +++ b/.github/agentic-refresh/governance.md @@ -96,6 +96,23 @@ With staging enabled, the compiled Phase 1 locks contain no repository write permission. Phase 2 must deliberately remove staging and revisit permissions before enabling real issue reporting. +## Phase 2A staged reporting guard + +The weekly reporting layer remains manual-only and report-only: + +- the deterministic orchestrator has `workflow_dispatch` but no schedule; +- all five Quest Master workflows accept reusable `workflow_call` inputs for an + ISO week and prior ISO week; +- the only agent-requested output is a custom job that validates and uploads a + run-scoped JSON artifact; +- the digest tolerates missing quest artifacts by recording the scope as + blocked, then creates deterministic parent and child previews as artifacts; +- no compiled or traditional workflow grants issue, branch, pull-request, + workflow-dispatch, or content write permission. + +The intended `0 8 * * 1` schedule and real issue safe outputs remain Phase 2B +activation work and require default-branch trials plus the bounded token spike. + ## SAML authorization SAML authorization is needed only for local audits and human GitHub operations. diff --git a/.github/agentic-refresh/report-contract.md b/.github/agentic-refresh/report-contract.md index 58a69d3..68c0e4f 100644 --- a/.github/agentic-refresh/report-contract.md +++ b/.github/agentic-refresh/report-contract.md @@ -3,6 +3,22 @@ Each weekly run produces one digest covering all five quests. It creates a child plan only for a material change. +Every quest run first emits one version 1 JSON artifact with: + +- ISO week key, prior ISO week key, and exact quest slug; +- exactly one result: `material-change`, `no-material-change`, or `blocked`; +- source facts separated from repository observations and proposed changes; +- lifecycle, allowlisted evidence URL, evidence location, wording, and source + fingerprint for every official-source delta; +- learner impact, owned-path drift, asset impact, report-only CodeTour impact, + validation, risks, human decisions, and prior finding keys; +- deterministic parent key, child key, markers, source fingerprints, and finding + keys added by `scripts/weekly-refresh-report.mjs`. + +A completed report must include source fingerprints. A blocked report must state +one concise reason and cannot propose changes. A no-change report cannot propose +changes. Invalid or missing quest artifacts become blocked scopes in the digest. + A child plan must contain: 1. Quest and ISO week key. @@ -23,3 +39,13 @@ Reports must distinguish source facts, repository observations, inferred impact, proposed changes, and unresolved uncertainty. Search snippets are not evidence. No implementation may start from a digest or an unapproved child plan. + +Phase 2A produces only run-scoped artifacts: + +- one validated JSON artifact per successful quest output; +- one deterministic digest JSON file; +- one parent issue Markdown preview; +- one child issue Markdown preview only for each material-change result. + +No Phase 2A workflow creates or updates an issue, comment, branch, pull request, +workflow dispatch, schedule, or content file. diff --git a/.github/agentic-refresh/tooling.json b/.github/agentic-refresh/tooling.json index d4f3180..c7da0f9 100644 --- a/.github/agentic-refresh/tooling.json +++ b/.github/agentic-refresh/tooling.json @@ -3,5 +3,7 @@ "ghAwVersion": "v0.77.5", "ghAwSetupAction": "github/gh-aw-actions/setup-cli@3ea13c02d765410340d533515cb31a7eef2baaf0", "checkoutAction": "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803", + "downloadArtifactAction": "actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c", + "uploadArtifactAction": "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a", "pythonVersion": "3.12" } diff --git a/.github/workflows/discover-agentic-rag.lock.yml b/.github/workflows/discover-agentic-rag.lock.yml index f02d610..3dff8ee 100644 --- a/.github/workflows/discover-agentic-rag.lock.yml +++ b/.github/workflows/discover-agentic-rag.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"582844232891b47171e31af990b11dfa91e37122049ec8cbb9b8ea3fba727c06","body_hash":"5ee7459c4e2c416a97f48adea9183ceb8ea02f5931434071379f56f44bdfddec","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"d109bf635ad05d7167f595bc20291e91b17af6b51d172d328e6db05c47164c4f","body_hash":"13d6da97cd8c2d7aa89f9cc8671e8303f2afb80e8d68ad1ff1e3f797fdacf735","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -28,6 +28,7 @@ # Imports: # - ../agents/agentic-rag-quest-master.agent.md # - shared/discovery-policy.md +# - shared/report-output.md # # Secrets used: # - GH_AW_GITHUB_MCP_SERVER_TOKEN @@ -53,6 +54,26 @@ name: "Discover Agentic RAG" on: + workflow_call: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string + secrets: + GH_AW_GITHUB_MCP_SERVER_TOKEN: + required: false + GH_AW_GITHUB_TOKEN: + required: false workflow_dispatch: inputs: aw_context: @@ -60,21 +81,33 @@ on: description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}" + cancel-in-progress: false + group: weekly-refresh-${{ inputs.week_key }}-agentic-rag run-name: "Discover Agentic RAG" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read outputs: + artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} comment_id: "" comment_repo: "" engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} @@ -84,6 +117,10 @@ jobs: setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + target_checkout_ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} + target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} + target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} + target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts id: setup @@ -91,12 +128,34 @@ jobs: with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Discover Agentic RAG" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-agentic-rag.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Resolve host repo for activation checkout + id: resolve-host-repo + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + JOB_WORKFLOW_REPOSITORY: ${{ job.workflow_repository }} + JOB_WORKFLOW_SHA: ${{ job.workflow_sha }} + JOB_WORKFLOW_REF: ${{ job.workflow_ref }} + JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs'); + await main(); + - name: Compute artifact prefix + id: artifact-prefix + env: + INPUTS_JSON: ${{ toJSON(inputs) }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: @@ -116,6 +175,7 @@ jobs: GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -123,10 +183,19 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Print cross-repo setup guidance + if: failure() && steps.resolve-host-repo.outputs.target_repo != github.repository + run: | + echo "::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets." + echo "::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow." + echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup" - name: Checkout .github and .agents folders + if: steps.resolve-host-repo.outputs.target_repo == github.repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + repository: ${{ steps.resolve-host-repo.outputs.target_repo }} + ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} sparse-checkout: | .github .agents @@ -179,27 +248,26 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF' + cat << 'GH_AW_PROMPT_398e46681d5ec93d_EOF' - GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF + GH_AW_PROMPT_398e46681d5ec93d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF' + cat << 'GH_AW_PROMPT_398e46681d5ec93d_EOF' - Tools: create_issue - GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF' + Tools: missing_tool, missing_data, noop, emit_weekly_report - GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF + GH_AW_PROMPT_398e46681d5ec93d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF' + cat << 'GH_AW_PROMPT_398e46681d5ec93d_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -228,21 +296,99 @@ jobs: {{/if}} - GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF + GH_AW_PROMPT_398e46681d5ec93d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF' + cat << 'GH_AW_PROMPT_398e46681d5ec93d_EOF' + Apply this staged weekly reporting contract for quest + `agentic-rag`: + + - Quest identity is `3`, + `LangChain.js Agentic RAG`, and + `quest/3-agentic-rag`. + - The reporting week is `__GH_AW_INPUTS_WEEK_KEY__` and the prior report locator is + `weekly-refresh:__GH_AW_INPUTS_PRIOR_WEEK_KEY__:agentic-rag`. + - Compare current allowed-source evidence, current Buildathon content, and any + prior finding keys available from the exact prior locator. Never broaden the + search to unrelated issues or repositories. + - Call `emit_weekly_report` exactly once, including blocked runs. The `result` + argument and `report_json.result` must match. + - Do not write a report file yourself. The deterministic safe-output job validates + the JSON and creates the run-scoped artifact. + + The `report_json` value must encode exactly this version 1 shape: + + ```json + { + "schemaVersion": 1, + "weekKey": "__GH_AW_INPUTS_WEEK_KEY__", + "priorWeekKey": "__GH_AW_INPUTS_PRIOR_WEEK_KEY__", + "questSlug": "agentic-rag", + "result": "material-change | no-material-change | blocked", + "executiveSummary": "short summary", + "currentQuestStatus": "current status", + "learnerImpact": "high | medium | low | none", + "officialSourceDeltas": [ + { + "fact": "source fact", + "lifecycle": "GA | Public preview | Private preview | Not stated by allowed sources", + "evidenceUrl": "allowlisted URL", + "evidenceLocation": "heading or section", + "observedWording": "short factual wording", + "fingerprint": "source fingerprint" + } + ], + "contentDrift": [ + { + "path": "owned Buildathon path", + "section": "section reference", + "observation": "repository observation" + } + ], + "proposedChanges": [ + { + "repository": "Azure-Samples/JavaScript-AI-Buildathon", + "path": "owned Buildathon path", + "description": "bounded proposed change" + } + ], + "assetImpact": "asset impact or empty string", + "codeTourImpact": [ + { + "repository": "exact external report-only repository", + "path": "exact observed CodeTour path", + "mode": "report-only", + "handoff": "manual-maintainer", + "observation": "bounded observation" + } + ], + "validationPlan": ["validation step"], + "risks": ["risk or uncertainty"], + "humanDecisions": ["decision required from Julia"], + "sourceFingerprints": ["source fingerprint"], + "priorFindingKeys": ["finding:<64 lowercase hex characters>"], + "blockedReason": null + } + ``` + + For `material-change`, include at least one proposed Buildathon change and use + `high`, `medium`, or `low` learner impact. For `no-material-change`, leave + `proposedChanges` empty. For `blocked`, set a concise `blockedReason`, leave + `proposedChanges` empty, and use `none` learner impact. + + {{#runtime-import .github/workflows/shared/discovery-policy.md}} {{#runtime-import .github/agents/agentic-rag-quest-master.agent.md}} {{#runtime-import .github/workflows/discover-agentic-rag.md}} - GH_AW_PROMPT_a3b6b9ea9b2518e0_EOF + GH_AW_PROMPT_398e46681d5ec93d_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -261,7 +407,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,7 +430,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + GH_AW_INPUTS_PRIOR_WEEK_KEY: process.env.GH_AW_INPUTS_PRIOR_WEEK_KEY, + GH_AW_INPUTS_WEEK_KEY: process.env.GH_AW_INPUTS_WEEK_KEY, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); - name: Validate prompt placeholders @@ -298,7 +450,7 @@ jobs: if: success() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: activation + name: ${{ steps.artifact-prefix.outputs.prefix }}activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json @@ -328,6 +480,7 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: discoveragenticrag outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} @@ -356,6 +509,7 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths id: set-runtime-paths run: | @@ -431,7 +585,7 @@ jobs: - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: activation + name: ${{ needs.activation.outputs.artifact_prefix }}activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' @@ -455,54 +609,117 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ad7c8308fa89812f_EOF' - {"create_issue":{"labels":["discover-agentic-rag"],"max":1,"title_prefix":"[discover-agentic-rag]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_ad7c8308fa89812f_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_feeffc626fdcdca3_EOF' + {"create_report_incomplete_issue":{},"emit-weekly-report":{"description":"Materialize one validated staged weekly quest report artifact","inputs":{"report_json":{"default":null,"description":"A JSON string matching the weekly quest report contract","required":true,"type":"string"},"result":{"default":null,"description":"The exact discovery result","options":["material-change","no-material-change","blocked"],"required":true,"type":"choice"}},"output":"The structured weekly quest report artifact was prepared"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_feeffc626fdcdca3_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { - "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[discover-agentic-rag]\". Labels [\"discover-agentic-rag\"] will be automatically added." - }, + "description_suffixes": {}, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [ + { + "description": "Materialize one validated staged weekly quest report artifact", + "inputSchema": { + "additionalProperties": false, + "properties": { + "report_json": { + "description": "A JSON string matching the weekly quest report contract", + "type": "string" + }, + "result": { + "description": "The exact discovery result", + "enum": [ + "material-change", + "no-material-change", + "blocked" + ], + "type": "string" + } + }, + "required": [ + "report_json", + "result" + ], + "type": "object" + }, + "name": "emit_weekly_report" + } + ] } GH_AW_VALIDATION_JSON: | { - "create_issue": { - "defaultMax": 1, + "missing_data": { + "defaultMax": 20, "fields": { - "body": { - "required": true, + "alternatives": { "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 256 }, - "fields": { - "type": "array" + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 }, - "parent": { - "issueOrPRNumber": true + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 }, - "repo": { + "reason": { + "required": true, "type": "string", + "sanitize": true, "maxLength": 256 }, - "temporary_id": { - "type": "string" + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 }, - "title": { + "reason": { "required": true, "type": "string", "sanitize": true, - "maxLength": 128 + "maxLength": 1024 } } } @@ -589,7 +806,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_ae2c34e6877fb55c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_452486d449d867e5_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -614,7 +831,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_ae2c34e6877fb55c_EOF + GH_AW_MCP_CONFIG_452486d449d867e5_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -870,7 +1087,7 @@ jobs: continue-on-error: true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: | /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/sandbox/agent/logs/ @@ -893,6 +1110,7 @@ jobs: needs: - activation - agent + - emit_weekly_report - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || @@ -900,9 +1118,14 @@ jobs: runs-on: ubuntu-slim permissions: {} concurrency: - group: "gh-aw-conclusion-discover-agentic-rag" + group: "gh-aw-conclusion-discover-agentic-rag-${{ inputs.week_key }}" cancel-in-progress: false queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts id: setup @@ -918,12 +1141,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -932,6 +1156,54 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Discover Agentic RAG" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-agentic-rag.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Agentic RAG" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-agentic-rag.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Agentic RAG" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-agentic-rag.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); - name: Handle agent failure id: handle_agent_failure if: always() @@ -970,6 +1242,86 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); + emit_weekly_report: + needs: + - agent + - agent + if: > + (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'emit_weekly_report') && + (always()) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.agent.outputs.artifact_prefix }}agent + path: ${{ runner.temp }}/gh-aw/safe-jobs/ + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Materialize structured quest report + run: | + node scripts/weekly-refresh-report.mjs materialize \ + --agent-output "$GH_AW_AGENT_OUTPUT" \ + --output "/tmp/weekly-refresh/${EXPECTED_QUEST_SLUG}.json" \ + --week "$EXPECTED_WEEK_KEY" \ + --prior-week "$EXPECTED_PRIOR_WEEK_KEY" \ + --quest "$EXPECTED_QUEST_SLUG" + env: + EXPECTED_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + EXPECTED_QUEST_SLUG: agentic-rag + EXPECTED_WEEK_KEY: ${{ inputs.week_key }} + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Upload structured quest report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + with: + if-no-files-found: error + name: weekly-refresh-${{ inputs.week_key }}-agentic-rag + path: /tmp/weekly-refresh/agentic-rag.json + retention-days: 30.0 + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Discover Agentic RAG" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-agentic-rag.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + safe_outputs: needs: - activation @@ -994,8 +1346,6 @@ jobs: code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1013,12 +1363,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -1045,7 +1396,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,docs.langchain.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"discover-agentic-rag\"],\"max\":1,\"title_prefix\":\"[discover-agentic-rag]\"}}" + GH_AW_SAFE_OUTPUT_JOBS: "{\"emit_weekly_report\":\"\"}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_SAFE_OUTPUTS_STAGED: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/discover-agentic-rag.md b/.github/workflows/discover-agentic-rag.md index 266ba99..4f63f5b 100644 --- a/.github/workflows/discover-agentic-rag.md +++ b/.github/workflows/discover-agentic-rag.md @@ -2,10 +2,33 @@ name: Discover Agentic RAG description: Manually audits the Agentic RAG quest and report-only CodeTours using approved evidence on: + workflow_call: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string workflow_dispatch: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string permissions: contents: read copilot-requests: write +concurrency: + group: weekly-refresh-${{ inputs.week_key }}-agentic-rag + cancel-in-progress: false + job-discriminator: ${{ inputs.week_key }} strict: true engine: id: copilot @@ -15,6 +38,12 @@ network: - docs.langchain.com imports: - shared/discovery-policy.md + - uses: shared/report-output.md + with: + quest-id: 3 + quest-slug: agentic-rag + quest-title: LangChain.js Agentic RAG + quest-label: quest/3-agentic-rag - ../agents/agentic-rag-quest-master.agent.md safe-outputs: activation-comments: false @@ -28,9 +57,9 @@ safe-outputs: timeout-minutes: 20 --- -# Phase 1 Agentic RAG Discovery +# Phase 2A Agentic RAG Reporting -Run a read-only manual trial for quest slug `agentic-rag`. +Run a read-only staged report for quest slug `agentic-rag`. ## Execution @@ -43,15 +72,19 @@ Run a read-only manual trial for quest slug `agentic-rag`. 5. Invoke `external-codetour-auditor` once for the six report-only CodeTours. 6. Reconcile all reports. Reject unsupported Agentic RAG classifications and make uncertainty explicit. -7. Return one concise GitHub-flavored Markdown report. Do not create or update - any persistent resource. +7. Compare against any exact prior finding keys supplied for + `${{ inputs.prior_week_key }}`. +8. Call `emit_weekly_report` exactly once with the structured report. +9. Return one concise GitHub-flavored Markdown summary. Do not create or update + repository content or GitHub issues, comments, branches, or pull requests. ## Final report contract Start nested headings at `###` and include: - `Result: material-change | no-material-change | blocked` -- Trial key `phase1-${{ github.run_id }}` and quest identity +- Week key `${{ inputs.week_key }}`, prior week `${{ inputs.prior_week_key }}`, + and quest identity - Executive summary and current quest status - Official-source facts and deltas with lifecycle, URL, location, observed wording, and fingerprint diff --git a/.github/workflows/discover-context-engineering.lock.yml b/.github/workflows/discover-context-engineering.lock.yml index 6643f33..ca61f86 100644 --- a/.github/workflows/discover-context-engineering.lock.yml +++ b/.github/workflows/discover-context-engineering.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"49e8926bde3532391b4f603f09f68e31db69e9379b7aecbadb2e465e6feb3c81","body_hash":"fd15df223f45e6cdf1e8a4dff08cbbd68131cac1f4d358085d19c7191df69db6","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"05b1ef12178775faab2f5571cb01e1a38e248e51bf2d4c3349a7b3eae712ccbc","body_hash":"b851cd0d53032fed7b6ca9c75ef5fdb047d2756d885b2ce996251f9d1d280657","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -28,6 +28,7 @@ # Imports: # - ../agents/context-engineering-quest-master.agent.md # - shared/discovery-policy.md +# - shared/report-output.md # # Secrets used: # - GH_AW_GITHUB_MCP_SERVER_TOKEN @@ -53,6 +54,26 @@ name: "Discover Context Engineering" on: + workflow_call: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string + secrets: + GH_AW_GITHUB_MCP_SERVER_TOKEN: + required: false + GH_AW_GITHUB_TOKEN: + required: false workflow_dispatch: inputs: aw_context: @@ -60,21 +81,33 @@ on: description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}" + cancel-in-progress: false + group: weekly-refresh-${{ inputs.week_key }}-context-engineering run-name: "Discover Context Engineering" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read outputs: + artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} comment_id: "" comment_repo: "" engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} @@ -84,6 +117,10 @@ jobs: setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + target_checkout_ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} + target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} + target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} + target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts id: setup @@ -91,12 +128,34 @@ jobs: with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Discover Context Engineering" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-context-engineering.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Resolve host repo for activation checkout + id: resolve-host-repo + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + JOB_WORKFLOW_REPOSITORY: ${{ job.workflow_repository }} + JOB_WORKFLOW_SHA: ${{ job.workflow_sha }} + JOB_WORKFLOW_REF: ${{ job.workflow_ref }} + JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs'); + await main(); + - name: Compute artifact prefix + id: artifact-prefix + env: + INPUTS_JSON: ${{ toJSON(inputs) }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: @@ -116,6 +175,7 @@ jobs: GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -123,10 +183,19 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Print cross-repo setup guidance + if: failure() && steps.resolve-host-repo.outputs.target_repo != github.repository + run: | + echo "::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets." + echo "::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow." + echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup" - name: Checkout .github and .agents folders + if: steps.resolve-host-repo.outputs.target_repo == github.repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + repository: ${{ steps.resolve-host-repo.outputs.target_repo }} + ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} sparse-checkout: | .github .agents @@ -179,27 +248,26 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_acfb4f332e62d21b_EOF' + cat << 'GH_AW_PROMPT_2c381e60a0e5db24_EOF' - GH_AW_PROMPT_acfb4f332e62d21b_EOF + GH_AW_PROMPT_2c381e60a0e5db24_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_acfb4f332e62d21b_EOF' + cat << 'GH_AW_PROMPT_2c381e60a0e5db24_EOF' - Tools: create_issue - GH_AW_PROMPT_acfb4f332e62d21b_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_acfb4f332e62d21b_EOF' + Tools: missing_tool, missing_data, noop, emit_weekly_report - GH_AW_PROMPT_acfb4f332e62d21b_EOF + GH_AW_PROMPT_2c381e60a0e5db24_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_acfb4f332e62d21b_EOF' + cat << 'GH_AW_PROMPT_2c381e60a0e5db24_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -228,21 +296,99 @@ jobs: {{/if}} - GH_AW_PROMPT_acfb4f332e62d21b_EOF + GH_AW_PROMPT_2c381e60a0e5db24_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_acfb4f332e62d21b_EOF' + cat << 'GH_AW_PROMPT_2c381e60a0e5db24_EOF' + Apply this staged weekly reporting contract for quest + `context-engineering`: + + - Quest identity is `5`, + `Context Engineering in Agents`, and + `quest/5-context-engineering`. + - The reporting week is `__GH_AW_INPUTS_WEEK_KEY__` and the prior report locator is + `weekly-refresh:__GH_AW_INPUTS_PRIOR_WEEK_KEY__:context-engineering`. + - Compare current allowed-source evidence, current Buildathon content, and any + prior finding keys available from the exact prior locator. Never broaden the + search to unrelated issues or repositories. + - Call `emit_weekly_report` exactly once, including blocked runs. The `result` + argument and `report_json.result` must match. + - Do not write a report file yourself. The deterministic safe-output job validates + the JSON and creates the run-scoped artifact. + + The `report_json` value must encode exactly this version 1 shape: + + ```json + { + "schemaVersion": 1, + "weekKey": "__GH_AW_INPUTS_WEEK_KEY__", + "priorWeekKey": "__GH_AW_INPUTS_PRIOR_WEEK_KEY__", + "questSlug": "context-engineering", + "result": "material-change | no-material-change | blocked", + "executiveSummary": "short summary", + "currentQuestStatus": "current status", + "learnerImpact": "high | medium | low | none", + "officialSourceDeltas": [ + { + "fact": "source fact", + "lifecycle": "GA | Public preview | Private preview | Not stated by allowed sources", + "evidenceUrl": "allowlisted URL", + "evidenceLocation": "heading or section", + "observedWording": "short factual wording", + "fingerprint": "source fingerprint" + } + ], + "contentDrift": [ + { + "path": "owned Buildathon path", + "section": "section reference", + "observation": "repository observation" + } + ], + "proposedChanges": [ + { + "repository": "Azure-Samples/JavaScript-AI-Buildathon", + "path": "owned Buildathon path", + "description": "bounded proposed change" + } + ], + "assetImpact": "asset impact or empty string", + "codeTourImpact": [ + { + "repository": "exact external report-only repository", + "path": "exact observed CodeTour path", + "mode": "report-only", + "handoff": "manual-maintainer", + "observation": "bounded observation" + } + ], + "validationPlan": ["validation step"], + "risks": ["risk or uncertainty"], + "humanDecisions": ["decision required from Julia"], + "sourceFingerprints": ["source fingerprint"], + "priorFindingKeys": ["finding:<64 lowercase hex characters>"], + "blockedReason": null + } + ``` + + For `material-change`, include at least one proposed Buildathon change and use + `high`, `medium`, or `low` learner impact. For `no-material-change`, leave + `proposedChanges` empty. For `blocked`, set a concise `blockedReason`, leave + `proposedChanges` empty, and use `none` learner impact. + + {{#runtime-import .github/workflows/shared/discovery-policy.md}} {{#runtime-import .github/agents/context-engineering-quest-master.agent.md}} {{#runtime-import .github/workflows/discover-context-engineering.md}} - GH_AW_PROMPT_acfb4f332e62d21b_EOF + GH_AW_PROMPT_2c381e60a0e5db24_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -261,7 +407,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,7 +430,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + GH_AW_INPUTS_PRIOR_WEEK_KEY: process.env.GH_AW_INPUTS_PRIOR_WEEK_KEY, + GH_AW_INPUTS_WEEK_KEY: process.env.GH_AW_INPUTS_WEEK_KEY, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); - name: Validate prompt placeholders @@ -298,7 +450,7 @@ jobs: if: success() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: activation + name: ${{ steps.artifact-prefix.outputs.prefix }}activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json @@ -328,6 +480,7 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: discovercontextengineering outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} @@ -356,6 +509,7 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths id: set-runtime-paths run: | @@ -431,7 +585,7 @@ jobs: - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: activation + name: ${{ needs.activation.outputs.artifact_prefix }}activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' @@ -455,54 +609,117 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_64043bc31bf5d344_EOF' - {"create_issue":{"labels":["discover-context-engineering"],"max":1,"title_prefix":"[discover-context-engineering]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_64043bc31bf5d344_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f358afc67f339d4a_EOF' + {"create_report_incomplete_issue":{},"emit-weekly-report":{"description":"Materialize one validated staged weekly quest report artifact","inputs":{"report_json":{"default":null,"description":"A JSON string matching the weekly quest report contract","required":true,"type":"string"},"result":{"default":null,"description":"The exact discovery result","options":["material-change","no-material-change","blocked"],"required":true,"type":"choice"}},"output":"The structured weekly quest report artifact was prepared"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_f358afc67f339d4a_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { - "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[discover-context-engineering]\". Labels [\"discover-context-engineering\"] will be automatically added." - }, + "description_suffixes": {}, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [ + { + "description": "Materialize one validated staged weekly quest report artifact", + "inputSchema": { + "additionalProperties": false, + "properties": { + "report_json": { + "description": "A JSON string matching the weekly quest report contract", + "type": "string" + }, + "result": { + "description": "The exact discovery result", + "enum": [ + "material-change", + "no-material-change", + "blocked" + ], + "type": "string" + } + }, + "required": [ + "report_json", + "result" + ], + "type": "object" + }, + "name": "emit_weekly_report" + } + ] } GH_AW_VALIDATION_JSON: | { - "create_issue": { - "defaultMax": 1, + "missing_data": { + "defaultMax": 20, "fields": { - "body": { - "required": true, + "alternatives": { "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 256 }, - "fields": { - "type": "array" + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 }, - "parent": { - "issueOrPRNumber": true + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 }, - "repo": { + "reason": { + "required": true, "type": "string", + "sanitize": true, "maxLength": 256 }, - "temporary_id": { - "type": "string" + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 }, - "title": { + "reason": { "required": true, "type": "string", "sanitize": true, - "maxLength": 128 + "maxLength": 1024 } } } @@ -589,7 +806,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d614d33ae7fd2f23_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_63f2df9a30913f38_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -614,7 +831,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_d614d33ae7fd2f23_EOF + GH_AW_MCP_CONFIG_63f2df9a30913f38_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -870,7 +1087,7 @@ jobs: continue-on-error: true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: | /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/sandbox/agent/logs/ @@ -893,6 +1110,7 @@ jobs: needs: - activation - agent + - emit_weekly_report - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || @@ -900,9 +1118,14 @@ jobs: runs-on: ubuntu-slim permissions: {} concurrency: - group: "gh-aw-conclusion-discover-context-engineering" + group: "gh-aw-conclusion-discover-context-engineering-${{ inputs.week_key }}" cancel-in-progress: false queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts id: setup @@ -918,12 +1141,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -932,6 +1156,54 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Discover Context Engineering" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-context-engineering.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Context Engineering" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-context-engineering.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Context Engineering" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-context-engineering.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); - name: Handle agent failure id: handle_agent_failure if: always() @@ -970,6 +1242,86 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); + emit_weekly_report: + needs: + - agent + - agent + if: > + (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'emit_weekly_report') && + (always()) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.agent.outputs.artifact_prefix }}agent + path: ${{ runner.temp }}/gh-aw/safe-jobs/ + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Materialize structured quest report + run: | + node scripts/weekly-refresh-report.mjs materialize \ + --agent-output "$GH_AW_AGENT_OUTPUT" \ + --output "/tmp/weekly-refresh/${EXPECTED_QUEST_SLUG}.json" \ + --week "$EXPECTED_WEEK_KEY" \ + --prior-week "$EXPECTED_PRIOR_WEEK_KEY" \ + --quest "$EXPECTED_QUEST_SLUG" + env: + EXPECTED_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + EXPECTED_QUEST_SLUG: context-engineering + EXPECTED_WEEK_KEY: ${{ inputs.week_key }} + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Upload structured quest report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + with: + if-no-files-found: error + name: weekly-refresh-${{ inputs.week_key }}-context-engineering + path: /tmp/weekly-refresh/context-engineering.json + retention-days: 30.0 + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Discover Context Engineering" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-context-engineering.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + safe_outputs: needs: - activation @@ -994,8 +1346,6 @@ jobs: code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1013,12 +1363,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -1045,7 +1396,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,docs.langchain.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,modelcontextprotocol.io,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"discover-context-engineering\"],\"max\":1,\"title_prefix\":\"[discover-context-engineering]\"}}" + GH_AW_SAFE_OUTPUT_JOBS: "{\"emit_weekly_report\":\"\"}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_SAFE_OUTPUTS_STAGED: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/discover-context-engineering.md b/.github/workflows/discover-context-engineering.md index 75ec0ec..92ce498 100644 --- a/.github/workflows/discover-context-engineering.md +++ b/.github/workflows/discover-context-engineering.md @@ -2,10 +2,33 @@ name: Discover Context Engineering description: Manually audits the Context Engineering quest and report-only CodeTours using approved evidence on: + workflow_call: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string workflow_dispatch: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string permissions: contents: read copilot-requests: write +concurrency: + group: weekly-refresh-${{ inputs.week_key }}-context-engineering + cancel-in-progress: false + job-discriminator: ${{ inputs.week_key }} strict: true engine: id: copilot @@ -16,6 +39,12 @@ network: - modelcontextprotocol.io imports: - shared/discovery-policy.md + - uses: shared/report-output.md + with: + quest-id: 5 + quest-slug: context-engineering + quest-title: Context Engineering in Agents + quest-label: quest/5-context-engineering - ../agents/context-engineering-quest-master.agent.md safe-outputs: activation-comments: false @@ -29,9 +58,9 @@ safe-outputs: timeout-minutes: 20 --- -# Phase 1 Context Engineering Discovery +# Phase 2A Context Engineering Reporting -Run a read-only manual trial for quest slug `context-engineering`. +Run a read-only staged report for quest slug `context-engineering`. ## Execution @@ -44,15 +73,19 @@ Run a read-only manual trial for quest slug `context-engineering`. 5. Invoke `external-codetour-auditor` once for the six report-only CodeTours. 6. Reconcile all reports. Reject unsupported claims and make uncertainty explicit. -7. Return one concise GitHub-flavored Markdown report. Do not create or update - any persistent resource. +7. Compare against any exact prior finding keys supplied for + `${{ inputs.prior_week_key }}`. +8. Call `emit_weekly_report` exactly once with the structured report. +9. Return one concise GitHub-flavored Markdown summary. Do not create or update + repository content or GitHub issues, comments, branches, or pull requests. ## Final report contract Start nested headings at `###` and include: - `Result: material-change | no-material-change | blocked` -- Trial key `phase1-${{ github.run_id }}` and quest identity +- Week key `${{ inputs.week_key }}`, prior week `${{ inputs.prior_week_key }}`, + and quest identity - Executive summary and current quest status - Official-source facts and deltas with lifecycle, URL, location, observed wording, and fingerprint diff --git a/.github/workflows/discover-foundry-local.lock.yml b/.github/workflows/discover-foundry-local.lock.yml index 18a38f0..e5da413 100644 --- a/.github/workflows/discover-foundry-local.lock.yml +++ b/.github/workflows/discover-foundry-local.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"ffd28d981373a6a3e08279a639a6a3f0d680b7561ad2ef060ee52c3fe4c4d19f","body_hash":"d5b8d31dfbb2a1824f25a64bfb4185f85c4fde16b39a8fab676f9fea654c87aa","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c15789717e0016e7b90433c3d4b068fa9e1cc8e688358bbbc8ae31a65c2c9691","body_hash":"83f53d44d855013681ddd0ded4e1003eb2e7ed2c08df6651e06e4c2d3c497d21","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -28,6 +28,7 @@ # Imports: # - ../agents/foundry-local-quest-master.agent.md # - shared/discovery-policy.md +# - shared/report-output.md # # Secrets used: # - GH_AW_GITHUB_MCP_SERVER_TOKEN @@ -53,6 +54,26 @@ name: "Discover Foundry Local" on: + workflow_call: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string + secrets: + GH_AW_GITHUB_MCP_SERVER_TOKEN: + required: false + GH_AW_GITHUB_TOKEN: + required: false workflow_dispatch: inputs: aw_context: @@ -60,21 +81,33 @@ on: description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}" + cancel-in-progress: false + group: weekly-refresh-${{ inputs.week_key }}-foundry-local run-name: "Discover Foundry Local" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read outputs: + artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} comment_id: "" comment_repo: "" engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} @@ -84,6 +117,10 @@ jobs: setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + target_checkout_ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} + target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} + target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} + target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts id: setup @@ -91,12 +128,34 @@ jobs: with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Discover Foundry Local" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-foundry-local.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Resolve host repo for activation checkout + id: resolve-host-repo + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + JOB_WORKFLOW_REPOSITORY: ${{ job.workflow_repository }} + JOB_WORKFLOW_SHA: ${{ job.workflow_sha }} + JOB_WORKFLOW_REF: ${{ job.workflow_ref }} + JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs'); + await main(); + - name: Compute artifact prefix + id: artifact-prefix + env: + INPUTS_JSON: ${{ toJSON(inputs) }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: @@ -116,6 +175,7 @@ jobs: GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -123,10 +183,19 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Print cross-repo setup guidance + if: failure() && steps.resolve-host-repo.outputs.target_repo != github.repository + run: | + echo "::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets." + echo "::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow." + echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup" - name: Checkout .github and .agents folders + if: steps.resolve-host-repo.outputs.target_repo == github.repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + repository: ${{ steps.resolve-host-repo.outputs.target_repo }} + ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} sparse-checkout: | .github .agents @@ -179,27 +248,26 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_2f12f4a6d80d4765_EOF' + cat << 'GH_AW_PROMPT_bd2bdf51cc699cb9_EOF' - GH_AW_PROMPT_2f12f4a6d80d4765_EOF + GH_AW_PROMPT_bd2bdf51cc699cb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_2f12f4a6d80d4765_EOF' + cat << 'GH_AW_PROMPT_bd2bdf51cc699cb9_EOF' - Tools: create_issue - GH_AW_PROMPT_2f12f4a6d80d4765_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_2f12f4a6d80d4765_EOF' + Tools: missing_tool, missing_data, noop, emit_weekly_report - GH_AW_PROMPT_2f12f4a6d80d4765_EOF + GH_AW_PROMPT_bd2bdf51cc699cb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_2f12f4a6d80d4765_EOF' + cat << 'GH_AW_PROMPT_bd2bdf51cc699cb9_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -228,21 +296,99 @@ jobs: {{/if}} - GH_AW_PROMPT_2f12f4a6d80d4765_EOF + GH_AW_PROMPT_bd2bdf51cc699cb9_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_2f12f4a6d80d4765_EOF' + cat << 'GH_AW_PROMPT_bd2bdf51cc699cb9_EOF' + Apply this staged weekly reporting contract for quest + `foundry-local`: + + - Quest identity is `1`, + `Foundry Local`, and + `quest/1-foundry-local`. + - The reporting week is `__GH_AW_INPUTS_WEEK_KEY__` and the prior report locator is + `weekly-refresh:__GH_AW_INPUTS_PRIOR_WEEK_KEY__:foundry-local`. + - Compare current allowed-source evidence, current Buildathon content, and any + prior finding keys available from the exact prior locator. Never broaden the + search to unrelated issues or repositories. + - Call `emit_weekly_report` exactly once, including blocked runs. The `result` + argument and `report_json.result` must match. + - Do not write a report file yourself. The deterministic safe-output job validates + the JSON and creates the run-scoped artifact. + + The `report_json` value must encode exactly this version 1 shape: + + ```json + { + "schemaVersion": 1, + "weekKey": "__GH_AW_INPUTS_WEEK_KEY__", + "priorWeekKey": "__GH_AW_INPUTS_PRIOR_WEEK_KEY__", + "questSlug": "foundry-local", + "result": "material-change | no-material-change | blocked", + "executiveSummary": "short summary", + "currentQuestStatus": "current status", + "learnerImpact": "high | medium | low | none", + "officialSourceDeltas": [ + { + "fact": "source fact", + "lifecycle": "GA | Public preview | Private preview | Not stated by allowed sources", + "evidenceUrl": "allowlisted URL", + "evidenceLocation": "heading or section", + "observedWording": "short factual wording", + "fingerprint": "source fingerprint" + } + ], + "contentDrift": [ + { + "path": "owned Buildathon path", + "section": "section reference", + "observation": "repository observation" + } + ], + "proposedChanges": [ + { + "repository": "Azure-Samples/JavaScript-AI-Buildathon", + "path": "owned Buildathon path", + "description": "bounded proposed change" + } + ], + "assetImpact": "asset impact or empty string", + "codeTourImpact": [ + { + "repository": "exact external report-only repository", + "path": "exact observed CodeTour path", + "mode": "report-only", + "handoff": "manual-maintainer", + "observation": "bounded observation" + } + ], + "validationPlan": ["validation step"], + "risks": ["risk or uncertainty"], + "humanDecisions": ["decision required from Julia"], + "sourceFingerprints": ["source fingerprint"], + "priorFindingKeys": ["finding:<64 lowercase hex characters>"], + "blockedReason": null + } + ``` + + For `material-change`, include at least one proposed Buildathon change and use + `high`, `medium`, or `low` learner impact. For `no-material-change`, leave + `proposedChanges` empty. For `blocked`, set a concise `blockedReason`, leave + `proposedChanges` empty, and use `none` learner impact. + + {{#runtime-import .github/workflows/shared/discovery-policy.md}} {{#runtime-import .github/agents/foundry-local-quest-master.agent.md}} {{#runtime-import .github/workflows/discover-foundry-local.md}} - GH_AW_PROMPT_2f12f4a6d80d4765_EOF + GH_AW_PROMPT_bd2bdf51cc699cb9_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -261,7 +407,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,7 +430,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + GH_AW_INPUTS_PRIOR_WEEK_KEY: process.env.GH_AW_INPUTS_PRIOR_WEEK_KEY, + GH_AW_INPUTS_WEEK_KEY: process.env.GH_AW_INPUTS_WEEK_KEY, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); - name: Validate prompt placeholders @@ -298,7 +450,7 @@ jobs: if: success() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: activation + name: ${{ steps.artifact-prefix.outputs.prefix }}activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json @@ -328,6 +480,7 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: discoverfoundrylocal outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} @@ -356,6 +509,7 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths id: set-runtime-paths run: | @@ -431,7 +585,7 @@ jobs: - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: activation + name: ${{ needs.activation.outputs.artifact_prefix }}activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' @@ -455,54 +609,117 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_60082d2bd72576f5_EOF' - {"create_issue":{"labels":["discover-foundry-local"],"max":1,"title_prefix":"[discover-foundry-local]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_60082d2bd72576f5_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4483f6c7d573195b_EOF' + {"create_report_incomplete_issue":{},"emit-weekly-report":{"description":"Materialize one validated staged weekly quest report artifact","inputs":{"report_json":{"default":null,"description":"A JSON string matching the weekly quest report contract","required":true,"type":"string"},"result":{"default":null,"description":"The exact discovery result","options":["material-change","no-material-change","blocked"],"required":true,"type":"choice"}},"output":"The structured weekly quest report artifact was prepared"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_4483f6c7d573195b_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { - "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[discover-foundry-local]\". Labels [\"discover-foundry-local\"] will be automatically added." - }, + "description_suffixes": {}, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [ + { + "description": "Materialize one validated staged weekly quest report artifact", + "inputSchema": { + "additionalProperties": false, + "properties": { + "report_json": { + "description": "A JSON string matching the weekly quest report contract", + "type": "string" + }, + "result": { + "description": "The exact discovery result", + "enum": [ + "material-change", + "no-material-change", + "blocked" + ], + "type": "string" + } + }, + "required": [ + "report_json", + "result" + ], + "type": "object" + }, + "name": "emit_weekly_report" + } + ] } GH_AW_VALIDATION_JSON: | { - "create_issue": { - "defaultMax": 1, + "missing_data": { + "defaultMax": 20, "fields": { - "body": { - "required": true, + "alternatives": { "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 256 }, - "fields": { - "type": "array" + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 }, - "parent": { - "issueOrPRNumber": true + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 }, - "repo": { + "reason": { + "required": true, "type": "string", + "sanitize": true, "maxLength": 256 }, - "temporary_id": { - "type": "string" + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 }, - "title": { + "reason": { "required": true, "type": "string", "sanitize": true, - "maxLength": 128 + "maxLength": 1024 } } } @@ -589,7 +806,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_c8b7aebc66316797_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f5b081f3a914d617_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -614,7 +831,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c8b7aebc66316797_EOF + GH_AW_MCP_CONFIG_f5b081f3a914d617_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -870,7 +1087,7 @@ jobs: continue-on-error: true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: | /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/sandbox/agent/logs/ @@ -893,6 +1110,7 @@ jobs: needs: - activation - agent + - emit_weekly_report - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || @@ -900,9 +1118,14 @@ jobs: runs-on: ubuntu-slim permissions: {} concurrency: - group: "gh-aw-conclusion-discover-foundry-local" + group: "gh-aw-conclusion-discover-foundry-local-${{ inputs.week_key }}" cancel-in-progress: false queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts id: setup @@ -918,12 +1141,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -932,6 +1156,54 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Discover Foundry Local" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-foundry-local.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Foundry Local" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-foundry-local.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Foundry Local" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-foundry-local.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); - name: Handle agent failure id: handle_agent_failure if: always() @@ -970,6 +1242,86 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); + emit_weekly_report: + needs: + - agent + - agent + if: > + (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'emit_weekly_report') && + (always()) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.agent.outputs.artifact_prefix }}agent + path: ${{ runner.temp }}/gh-aw/safe-jobs/ + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Materialize structured quest report + run: | + node scripts/weekly-refresh-report.mjs materialize \ + --agent-output "$GH_AW_AGENT_OUTPUT" \ + --output "/tmp/weekly-refresh/${EXPECTED_QUEST_SLUG}.json" \ + --week "$EXPECTED_WEEK_KEY" \ + --prior-week "$EXPECTED_PRIOR_WEEK_KEY" \ + --quest "$EXPECTED_QUEST_SLUG" + env: + EXPECTED_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + EXPECTED_QUEST_SLUG: foundry-local + EXPECTED_WEEK_KEY: ${{ inputs.week_key }} + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Upload structured quest report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + with: + if-no-files-found: error + name: weekly-refresh-${{ inputs.week_key }}-foundry-local + path: /tmp/weekly-refresh/foundry-local.json + retention-days: 30.0 + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Discover Foundry Local" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-foundry-local.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + safe_outputs: needs: - activation @@ -994,8 +1346,6 @@ jobs: code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1013,12 +1363,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -1045,7 +1396,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.foundrylocal.ai,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"discover-foundry-local\"],\"max\":1,\"title_prefix\":\"[discover-foundry-local]\"}}" + GH_AW_SAFE_OUTPUT_JOBS: "{\"emit_weekly_report\":\"\"}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_SAFE_OUTPUTS_STAGED: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/discover-foundry-local.md b/.github/workflows/discover-foundry-local.md index 528b8e6..0dc6c9a 100644 --- a/.github/workflows/discover-foundry-local.md +++ b/.github/workflows/discover-foundry-local.md @@ -2,10 +2,33 @@ name: Discover Foundry Local description: Manually audits the Foundry Local quest using read-only approved evidence on: + workflow_call: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string workflow_dispatch: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string permissions: contents: read copilot-requests: write +concurrency: + group: weekly-refresh-${{ inputs.week_key }}-foundry-local + cancel-in-progress: false + job-discriminator: ${{ inputs.week_key }} strict: true engine: id: copilot @@ -15,6 +38,12 @@ network: - www.foundrylocal.ai imports: - shared/discovery-policy.md + - uses: shared/report-output.md + with: + quest-id: 1 + quest-slug: foundry-local + quest-title: Foundry Local + quest-label: quest/1-foundry-local - ../agents/foundry-local-quest-master.agent.md safe-outputs: activation-comments: false @@ -28,9 +57,9 @@ safe-outputs: timeout-minutes: 20 --- -# Phase 1 Foundry Local Discovery +# Phase 2A Foundry Local Reporting -Run a read-only manual trial for quest slug `foundry-local`. +Run a read-only staged report for quest slug `foundry-local`. ## Execution @@ -42,15 +71,19 @@ Run a read-only manual trial for quest slug `foundry-local`. file and section references. 5. Reconcile both reports. Reject unsupported claims and make uncertainty explicit. -6. Return one concise GitHub-flavored Markdown report. Do not create or update - any persistent resource. +6. Compare against any exact prior finding keys supplied for + `${{ inputs.prior_week_key }}`. +7. Call `emit_weekly_report` exactly once with the structured report. +8. Return one concise GitHub-flavored Markdown summary. Do not create or update + repository content or GitHub issues, comments, branches, or pull requests. ## Final report contract Start nested headings at `###` and include: - `Result: material-change | no-material-change | blocked` -- Trial key `phase1-${{ github.run_id }}` and quest identity +- Week key `${{ inputs.week_key }}`, prior week `${{ inputs.prior_week_key }}`, + and quest identity - Executive summary and current quest status - Official-source facts and deltas with lifecycle, URL, location, observed wording, and fingerprint diff --git a/.github/workflows/discover-foundry-toolkit.lock.yml b/.github/workflows/discover-foundry-toolkit.lock.yml index 8d2c424..56e3616 100644 --- a/.github/workflows/discover-foundry-toolkit.lock.yml +++ b/.github/workflows/discover-foundry-toolkit.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"d40bf6b72bb59d3ea2ec5e77a70bfd087c2b186aa28464e7aeda540f528ddd2a","body_hash":"b0b0a8da1c790cf7d2927b55d1de770cff90a219da0feea563648b70b84f2f41","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"6c0b732fbf281a8abfe5690fceaa11600f1fd5ff3b7564865ddfe12679750563","body_hash":"acae829c960cd47d3dde1b3ab67b1b05f7bf070683d8809bb80913c4d53d3687","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -28,6 +28,7 @@ # Imports: # - ../agents/foundry-toolkit-quest-master.agent.md # - shared/discovery-policy.md +# - shared/report-output.md # # Secrets used: # - GH_AW_GITHUB_MCP_SERVER_TOKEN @@ -53,6 +54,26 @@ name: "Discover Foundry Toolkit" on: + workflow_call: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string + secrets: + GH_AW_GITHUB_MCP_SERVER_TOKEN: + required: false + GH_AW_GITHUB_TOKEN: + required: false workflow_dispatch: inputs: aw_context: @@ -60,21 +81,33 @@ on: description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}" + cancel-in-progress: false + group: weekly-refresh-${{ inputs.week_key }}-foundry-toolkit run-name: "Discover Foundry Toolkit" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read outputs: + artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} comment_id: "" comment_repo: "" engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} @@ -84,6 +117,10 @@ jobs: setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + target_checkout_ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} + target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} + target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} + target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts id: setup @@ -91,12 +128,34 @@ jobs: with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Discover Foundry Toolkit" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-foundry-toolkit.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Resolve host repo for activation checkout + id: resolve-host-repo + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + JOB_WORKFLOW_REPOSITORY: ${{ job.workflow_repository }} + JOB_WORKFLOW_SHA: ${{ job.workflow_sha }} + JOB_WORKFLOW_REF: ${{ job.workflow_ref }} + JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs'); + await main(); + - name: Compute artifact prefix + id: artifact-prefix + env: + INPUTS_JSON: ${{ toJSON(inputs) }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: @@ -116,6 +175,7 @@ jobs: GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -123,10 +183,19 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Print cross-repo setup guidance + if: failure() && steps.resolve-host-repo.outputs.target_repo != github.repository + run: | + echo "::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets." + echo "::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow." + echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup" - name: Checkout .github and .agents folders + if: steps.resolve-host-repo.outputs.target_repo == github.repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + repository: ${{ steps.resolve-host-repo.outputs.target_repo }} + ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} sparse-checkout: | .github .agents @@ -179,27 +248,26 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF' + cat << 'GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF' - GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF + GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF' + cat << 'GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF' - Tools: create_issue - GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF' + Tools: missing_tool, missing_data, noop, emit_weekly_report - GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF + GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF' + cat << 'GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -228,21 +296,99 @@ jobs: {{/if}} - GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF + GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF' + cat << 'GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF' + Apply this staged weekly reporting contract for quest + `foundry-toolkit`: + + - Quest identity is `4`, + `Microsoft Foundry Toolkit`, and + `quest/4-foundry-toolkit`. + - The reporting week is `__GH_AW_INPUTS_WEEK_KEY__` and the prior report locator is + `weekly-refresh:__GH_AW_INPUTS_PRIOR_WEEK_KEY__:foundry-toolkit`. + - Compare current allowed-source evidence, current Buildathon content, and any + prior finding keys available from the exact prior locator. Never broaden the + search to unrelated issues or repositories. + - Call `emit_weekly_report` exactly once, including blocked runs. The `result` + argument and `report_json.result` must match. + - Do not write a report file yourself. The deterministic safe-output job validates + the JSON and creates the run-scoped artifact. + + The `report_json` value must encode exactly this version 1 shape: + + ```json + { + "schemaVersion": 1, + "weekKey": "__GH_AW_INPUTS_WEEK_KEY__", + "priorWeekKey": "__GH_AW_INPUTS_PRIOR_WEEK_KEY__", + "questSlug": "foundry-toolkit", + "result": "material-change | no-material-change | blocked", + "executiveSummary": "short summary", + "currentQuestStatus": "current status", + "learnerImpact": "high | medium | low | none", + "officialSourceDeltas": [ + { + "fact": "source fact", + "lifecycle": "GA | Public preview | Private preview | Not stated by allowed sources", + "evidenceUrl": "allowlisted URL", + "evidenceLocation": "heading or section", + "observedWording": "short factual wording", + "fingerprint": "source fingerprint" + } + ], + "contentDrift": [ + { + "path": "owned Buildathon path", + "section": "section reference", + "observation": "repository observation" + } + ], + "proposedChanges": [ + { + "repository": "Azure-Samples/JavaScript-AI-Buildathon", + "path": "owned Buildathon path", + "description": "bounded proposed change" + } + ], + "assetImpact": "asset impact or empty string", + "codeTourImpact": [ + { + "repository": "exact external report-only repository", + "path": "exact observed CodeTour path", + "mode": "report-only", + "handoff": "manual-maintainer", + "observation": "bounded observation" + } + ], + "validationPlan": ["validation step"], + "risks": ["risk or uncertainty"], + "humanDecisions": ["decision required from Julia"], + "sourceFingerprints": ["source fingerprint"], + "priorFindingKeys": ["finding:<64 lowercase hex characters>"], + "blockedReason": null + } + ``` + + For `material-change`, include at least one proposed Buildathon change and use + `high`, `medium`, or `low` learner impact. For `no-material-change`, leave + `proposedChanges` empty. For `blocked`, set a concise `blockedReason`, leave + `proposedChanges` empty, and use `none` learner impact. + + {{#runtime-import .github/workflows/shared/discovery-policy.md}} {{#runtime-import .github/agents/foundry-toolkit-quest-master.agent.md}} {{#runtime-import .github/workflows/discover-foundry-toolkit.md}} - GH_AW_PROMPT_9fbb2bb06a9f0ab9_EOF + GH_AW_PROMPT_2fc5f82ad8c6f5f7_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -261,7 +407,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,7 +430,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + GH_AW_INPUTS_PRIOR_WEEK_KEY: process.env.GH_AW_INPUTS_PRIOR_WEEK_KEY, + GH_AW_INPUTS_WEEK_KEY: process.env.GH_AW_INPUTS_WEEK_KEY, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); - name: Validate prompt placeholders @@ -298,7 +450,7 @@ jobs: if: success() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: activation + name: ${{ steps.artifact-prefix.outputs.prefix }}activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json @@ -328,6 +480,7 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: discoverfoundrytoolkit outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} @@ -356,6 +509,7 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths id: set-runtime-paths run: | @@ -431,7 +585,7 @@ jobs: - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: activation + name: ${{ needs.activation.outputs.artifact_prefix }}activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' @@ -455,54 +609,117 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ff9e3236ff02e08a_EOF' - {"create_issue":{"labels":["discover-foundry-toolkit"],"max":1,"title_prefix":"[discover-foundry-toolkit]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_ff9e3236ff02e08a_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a240839364e73802_EOF' + {"create_report_incomplete_issue":{},"emit-weekly-report":{"description":"Materialize one validated staged weekly quest report artifact","inputs":{"report_json":{"default":null,"description":"A JSON string matching the weekly quest report contract","required":true,"type":"string"},"result":{"default":null,"description":"The exact discovery result","options":["material-change","no-material-change","blocked"],"required":true,"type":"choice"}},"output":"The structured weekly quest report artifact was prepared"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_a240839364e73802_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { - "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[discover-foundry-toolkit]\". Labels [\"discover-foundry-toolkit\"] will be automatically added." - }, + "description_suffixes": {}, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [ + { + "description": "Materialize one validated staged weekly quest report artifact", + "inputSchema": { + "additionalProperties": false, + "properties": { + "report_json": { + "description": "A JSON string matching the weekly quest report contract", + "type": "string" + }, + "result": { + "description": "The exact discovery result", + "enum": [ + "material-change", + "no-material-change", + "blocked" + ], + "type": "string" + } + }, + "required": [ + "report_json", + "result" + ], + "type": "object" + }, + "name": "emit_weekly_report" + } + ] } GH_AW_VALIDATION_JSON: | { - "create_issue": { - "defaultMax": 1, + "missing_data": { + "defaultMax": 20, "fields": { - "body": { - "required": true, + "alternatives": { "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 256 }, - "fields": { - "type": "array" + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 }, - "parent": { - "issueOrPRNumber": true + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 }, - "repo": { + "reason": { + "required": true, "type": "string", + "sanitize": true, "maxLength": 256 }, - "temporary_id": { - "type": "string" + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 }, - "title": { + "reason": { "required": true, "type": "string", "sanitize": true, - "maxLength": 128 + "maxLength": 1024 } } } @@ -589,7 +806,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_a867f96cd03f5afe_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_836d949c0b7b6e0c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -614,7 +831,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_a867f96cd03f5afe_EOF + GH_AW_MCP_CONFIG_836d949c0b7b6e0c_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -870,7 +1087,7 @@ jobs: continue-on-error: true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: | /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/sandbox/agent/logs/ @@ -893,6 +1110,7 @@ jobs: needs: - activation - agent + - emit_weekly_report - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || @@ -900,9 +1118,14 @@ jobs: runs-on: ubuntu-slim permissions: {} concurrency: - group: "gh-aw-conclusion-discover-foundry-toolkit" + group: "gh-aw-conclusion-discover-foundry-toolkit-${{ inputs.week_key }}" cancel-in-progress: false queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts id: setup @@ -918,12 +1141,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -932,6 +1156,54 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Discover Foundry Toolkit" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-foundry-toolkit.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Foundry Toolkit" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-foundry-toolkit.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Foundry Toolkit" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-foundry-toolkit.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); - name: Handle agent failure id: handle_agent_failure if: always() @@ -970,6 +1242,86 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); + emit_weekly_report: + needs: + - agent + - agent + if: > + (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'emit_weekly_report') && + (always()) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.agent.outputs.artifact_prefix }}agent + path: ${{ runner.temp }}/gh-aw/safe-jobs/ + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Materialize structured quest report + run: | + node scripts/weekly-refresh-report.mjs materialize \ + --agent-output "$GH_AW_AGENT_OUTPUT" \ + --output "/tmp/weekly-refresh/${EXPECTED_QUEST_SLUG}.json" \ + --week "$EXPECTED_WEEK_KEY" \ + --prior-week "$EXPECTED_PRIOR_WEEK_KEY" \ + --quest "$EXPECTED_QUEST_SLUG" + env: + EXPECTED_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + EXPECTED_QUEST_SLUG: foundry-toolkit + EXPECTED_WEEK_KEY: ${{ inputs.week_key }} + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Upload structured quest report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + with: + if-no-files-found: error + name: weekly-refresh-${{ inputs.week_key }}-foundry-toolkit + path: /tmp/weekly-refresh/foundry-toolkit.json + retention-days: 30.0 + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Discover Foundry Toolkit" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-foundry-toolkit.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + safe_outputs: needs: - activation @@ -994,8 +1346,6 @@ jobs: code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1013,12 +1363,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -1045,7 +1396,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,code.visualstudio.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,marketplace.visualstudio.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"discover-foundry-toolkit\"],\"max\":1,\"title_prefix\":\"[discover-foundry-toolkit]\"}}" + GH_AW_SAFE_OUTPUT_JOBS: "{\"emit_weekly_report\":\"\"}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_SAFE_OUTPUTS_STAGED: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/discover-foundry-toolkit.md b/.github/workflows/discover-foundry-toolkit.md index 1b54baf..3e06886 100644 --- a/.github/workflows/discover-foundry-toolkit.md +++ b/.github/workflows/discover-foundry-toolkit.md @@ -2,10 +2,33 @@ name: Discover Foundry Toolkit description: Manually audits the Microsoft Foundry Toolkit quest using read-only approved evidence on: + workflow_call: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string workflow_dispatch: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string permissions: contents: read copilot-requests: write +concurrency: + group: weekly-refresh-${{ inputs.week_key }}-foundry-toolkit + cancel-in-progress: false + job-discriminator: ${{ inputs.week_key }} strict: true engine: id: copilot @@ -16,6 +39,12 @@ network: - marketplace.visualstudio.com imports: - shared/discovery-policy.md + - uses: shared/report-output.md + with: + quest-id: 4 + quest-slug: foundry-toolkit + quest-title: Microsoft Foundry Toolkit + quest-label: quest/4-foundry-toolkit - ../agents/foundry-toolkit-quest-master.agent.md safe-outputs: activation-comments: false @@ -29,9 +58,9 @@ safe-outputs: timeout-minutes: 20 --- -# Phase 1 Foundry Toolkit Discovery +# Phase 2A Foundry Toolkit Reporting -Run a read-only manual trial for quest slug `foundry-toolkit`. +Run a read-only staged report for quest slug `foundry-toolkit`. ## Execution @@ -43,15 +72,19 @@ Run a read-only manual trial for quest slug `foundry-toolkit`. with exact file and section references. 5. Reconcile both reports. Reject unsupported claims and make uncertainty explicit. -6. Return one concise GitHub-flavored Markdown report. Do not create or update - any persistent resource. +6. Compare against any exact prior finding keys supplied for + `${{ inputs.prior_week_key }}`. +7. Call `emit_weekly_report` exactly once with the structured report. +8. Return one concise GitHub-flavored Markdown summary. Do not create or update + repository content or GitHub issues, comments, branches, or pull requests. ## Final report contract Start nested headings at `###` and include: - `Result: material-change | no-material-change | blocked` -- Trial key `phase1-${{ github.run_id }}` and quest identity +- Week key `${{ inputs.week_key }}`, prior week `${{ inputs.prior_week_key }}`, + and quest identity - Executive summary and current quest status - Official-source facts and deltas with lifecycle, URL, location, observed wording, and fingerprint diff --git a/.github/workflows/discover-microsoft-foundry.lock.yml b/.github/workflows/discover-microsoft-foundry.lock.yml index 488edcf..e1b22a5 100644 --- a/.github/workflows/discover-microsoft-foundry.lock.yml +++ b/.github/workflows/discover-microsoft-foundry.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9949cf859a994fad158037899da1fc71ddb04750a1a8cbc0a818659949834098","body_hash":"3198de2221e180925b129810b950355cffc9bf96294a6e71913c23bbe7d0bdff","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"908293d6c58ff680f62c316c5e46a2ece61960dfbab18446ef00ab9a791606a5","body_hash":"789d17f437d800703e837a41bb5d69ce26157e2eab55f7151de1c009ebfe7e9d","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -28,6 +28,7 @@ # Imports: # - ../agents/microsoft-foundry-quest-master.agent.md # - shared/discovery-policy.md +# - shared/report-output.md # # Secrets used: # - GH_AW_GITHUB_MCP_SERVER_TOKEN @@ -53,6 +54,26 @@ name: "Discover Microsoft Foundry" on: + workflow_call: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string + secrets: + GH_AW_GITHUB_MCP_SERVER_TOKEN: + required: false + GH_AW_GITHUB_TOKEN: + required: false workflow_dispatch: inputs: aw_context: @@ -60,21 +81,33 @@ on: description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}" + cancel-in-progress: false + group: weekly-refresh-${{ inputs.week_key }}-microsoft-foundry run-name: "Discover Microsoft Foundry" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: actions: read contents: read outputs: + artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} comment_id: "" comment_repo: "" engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} @@ -84,6 +117,10 @@ jobs: setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + target_checkout_ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} + target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} + target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} + target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts id: setup @@ -91,12 +128,34 @@ jobs: with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Discover Microsoft Foundry" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-microsoft-foundry.lock.yml@${{ github.ref }} GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Resolve host repo for activation checkout + id: resolve-host-repo + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + JOB_WORKFLOW_REPOSITORY: ${{ job.workflow_repository }} + JOB_WORKFLOW_SHA: ${{ job.workflow_sha }} + JOB_WORKFLOW_REF: ${{ job.workflow_ref }} + JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs'); + await main(); + - name: Compute artifact prefix + id: artifact-prefix + env: + INPUTS_JSON: ${{ toJSON(inputs) }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: @@ -116,6 +175,7 @@ jobs: GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -123,10 +183,19 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Print cross-repo setup guidance + if: failure() && steps.resolve-host-repo.outputs.target_repo != github.repository + run: | + echo "::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets." + echo "::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow." + echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup" - name: Checkout .github and .agents folders + if: steps.resolve-host-repo.outputs.target_repo == github.repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + repository: ${{ steps.resolve-host-repo.outputs.target_repo }} + ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} sparse-checkout: | .github .agents @@ -179,27 +248,26 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} # poutine:ignore untrusted_checkout_exec run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_5732786544e69446_EOF' + cat << 'GH_AW_PROMPT_bc6e89830fd3236a_EOF' - GH_AW_PROMPT_5732786544e69446_EOF + GH_AW_PROMPT_bc6e89830fd3236a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_5732786544e69446_EOF' + cat << 'GH_AW_PROMPT_bc6e89830fd3236a_EOF' - Tools: create_issue - GH_AW_PROMPT_5732786544e69446_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" - cat << 'GH_AW_PROMPT_5732786544e69446_EOF' + Tools: missing_tool, missing_data, noop, emit_weekly_report - GH_AW_PROMPT_5732786544e69446_EOF + GH_AW_PROMPT_bc6e89830fd3236a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_5732786544e69446_EOF' + cat << 'GH_AW_PROMPT_bc6e89830fd3236a_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -228,21 +296,99 @@ jobs: {{/if}} - GH_AW_PROMPT_5732786544e69446_EOF + GH_AW_PROMPT_bc6e89830fd3236a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_5732786544e69446_EOF' + cat << 'GH_AW_PROMPT_bc6e89830fd3236a_EOF' + Apply this staged weekly reporting contract for quest + `microsoft-foundry`: + + - Quest identity is `2`, + `Microsoft Foundry`, and + `quest/2-microsoft-foundry`. + - The reporting week is `__GH_AW_INPUTS_WEEK_KEY__` and the prior report locator is + `weekly-refresh:__GH_AW_INPUTS_PRIOR_WEEK_KEY__:microsoft-foundry`. + - Compare current allowed-source evidence, current Buildathon content, and any + prior finding keys available from the exact prior locator. Never broaden the + search to unrelated issues or repositories. + - Call `emit_weekly_report` exactly once, including blocked runs. The `result` + argument and `report_json.result` must match. + - Do not write a report file yourself. The deterministic safe-output job validates + the JSON and creates the run-scoped artifact. + + The `report_json` value must encode exactly this version 1 shape: + + ```json + { + "schemaVersion": 1, + "weekKey": "__GH_AW_INPUTS_WEEK_KEY__", + "priorWeekKey": "__GH_AW_INPUTS_PRIOR_WEEK_KEY__", + "questSlug": "microsoft-foundry", + "result": "material-change | no-material-change | blocked", + "executiveSummary": "short summary", + "currentQuestStatus": "current status", + "learnerImpact": "high | medium | low | none", + "officialSourceDeltas": [ + { + "fact": "source fact", + "lifecycle": "GA | Public preview | Private preview | Not stated by allowed sources", + "evidenceUrl": "allowlisted URL", + "evidenceLocation": "heading or section", + "observedWording": "short factual wording", + "fingerprint": "source fingerprint" + } + ], + "contentDrift": [ + { + "path": "owned Buildathon path", + "section": "section reference", + "observation": "repository observation" + } + ], + "proposedChanges": [ + { + "repository": "Azure-Samples/JavaScript-AI-Buildathon", + "path": "owned Buildathon path", + "description": "bounded proposed change" + } + ], + "assetImpact": "asset impact or empty string", + "codeTourImpact": [ + { + "repository": "exact external report-only repository", + "path": "exact observed CodeTour path", + "mode": "report-only", + "handoff": "manual-maintainer", + "observation": "bounded observation" + } + ], + "validationPlan": ["validation step"], + "risks": ["risk or uncertainty"], + "humanDecisions": ["decision required from Julia"], + "sourceFingerprints": ["source fingerprint"], + "priorFindingKeys": ["finding:<64 lowercase hex characters>"], + "blockedReason": null + } + ``` + + For `material-change`, include at least one proposed Buildathon change and use + `high`, `medium`, or `low` learner impact. For `no-material-change`, leave + `proposedChanges` empty. For `blocked`, set a concise `blockedReason`, leave + `proposedChanges` empty, and use `none` learner impact. + + {{#runtime-import .github/workflows/shared/discovery-policy.md}} {{#runtime-import .github/agents/microsoft-foundry-quest-master.agent.md}} {{#runtime-import .github/workflows/discover-microsoft-foundry.md}} - GH_AW_PROMPT_5732786544e69446_EOF + GH_AW_PROMPT_bc6e89830fd3236a_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -261,7 +407,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -281,7 +430,10 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + GH_AW_INPUTS_PRIOR_WEEK_KEY: process.env.GH_AW_INPUTS_PRIOR_WEEK_KEY, + GH_AW_INPUTS_WEEK_KEY: process.env.GH_AW_INPUTS_WEEK_KEY, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED } }); - name: Validate prompt placeholders @@ -298,7 +450,7 @@ jobs: if: success() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: activation + name: ${{ steps.artifact-prefix.outputs.prefix }}activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json @@ -328,6 +480,7 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: discovermicrosoftfoundry outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} @@ -356,6 +509,7 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Set runtime paths id: set-runtime-paths run: | @@ -431,7 +585,7 @@ jobs: - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: activation + name: ${{ needs.activation.outputs.artifact_prefix }}activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' @@ -455,54 +609,117 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_dfce5e0f0e76aafb_EOF' - {"create_issue":{"labels":["discover-microsoft-foundry"],"max":1,"title_prefix":"[discover-microsoft-foundry]"}} - GH_AW_SAFE_OUTPUTS_CONFIG_dfce5e0f0e76aafb_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_d711b77a2c3aa8e8_EOF' + {"create_report_incomplete_issue":{},"emit-weekly-report":{"description":"Materialize one validated staged weekly quest report artifact","inputs":{"report_json":{"default":null,"description":"A JSON string matching the weekly quest report contract","required":true,"type":"string"},"result":{"default":null,"description":"The exact discovery result","options":["material-change","no-material-change","blocked"],"required":true,"type":"choice"}},"output":"The structured weekly quest report artifact was prepared"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_d711b77a2c3aa8e8_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { - "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[discover-microsoft-foundry]\". Labels [\"discover-microsoft-foundry\"] will be automatically added." - }, + "description_suffixes": {}, "repo_params": {}, - "dynamic_tools": [] + "dynamic_tools": [ + { + "description": "Materialize one validated staged weekly quest report artifact", + "inputSchema": { + "additionalProperties": false, + "properties": { + "report_json": { + "description": "A JSON string matching the weekly quest report contract", + "type": "string" + }, + "result": { + "description": "The exact discovery result", + "enum": [ + "material-change", + "no-material-change", + "blocked" + ], + "type": "string" + } + }, + "required": [ + "report_json", + "result" + ], + "type": "object" + }, + "name": "emit_weekly_report" + } + ] } GH_AW_VALIDATION_JSON: | { - "create_issue": { - "defaultMax": 1, + "missing_data": { + "defaultMax": 20, "fields": { - "body": { - "required": true, + "alternatives": { "type": "string", "sanitize": true, - "maxLength": 65000 + "maxLength": 256 }, - "fields": { - "type": "array" + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 }, - "parent": { - "issueOrPRNumber": true + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 }, - "repo": { + "reason": { + "required": true, "type": "string", + "sanitize": true, "maxLength": 256 }, - "temporary_id": { - "type": "string" + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 }, - "title": { + "reason": { "required": true, "type": "string", "sanitize": true, - "maxLength": 128 + "maxLength": 1024 } } } @@ -589,7 +806,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_df5ea76d7fc4c8d3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_33f7c5d06774b0ca_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -614,7 +831,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_df5ea76d7fc4c8d3_EOF + GH_AW_MCP_CONFIG_33f7c5d06774b0ca_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -870,7 +1087,7 @@ jobs: continue-on-error: true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: | /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/sandbox/agent/logs/ @@ -893,6 +1110,7 @@ jobs: needs: - activation - agent + - emit_weekly_report - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || @@ -900,9 +1118,14 @@ jobs: runs-on: ubuntu-slim permissions: {} concurrency: - group: "gh-aw-conclusion-discover-microsoft-foundry" + group: "gh-aw-conclusion-discover-microsoft-foundry-${{ inputs.week_key }}" cancel-in-progress: false queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts id: setup @@ -918,12 +1141,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -932,6 +1156,54 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Discover Microsoft Foundry" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-microsoft-foundry.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Microsoft Foundry" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-microsoft-foundry.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Discover Microsoft Foundry" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/discover-microsoft-foundry.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); - name: Handle agent failure id: handle_agent_failure if: always() @@ -970,6 +1242,86 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); + emit_weekly_report: + needs: + - agent + - agent + if: > + (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'emit_weekly_report') && + (always()) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.agent.outputs.artifact_prefix }}agent + path: ${{ runner.temp }}/gh-aw/safe-jobs/ + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Materialize structured quest report + run: | + node scripts/weekly-refresh-report.mjs materialize \ + --agent-output "$GH_AW_AGENT_OUTPUT" \ + --output "/tmp/weekly-refresh/${EXPECTED_QUEST_SLUG}.json" \ + --week "$EXPECTED_WEEK_KEY" \ + --prior-week "$EXPECTED_PRIOR_WEEK_KEY" \ + --quest "$EXPECTED_QUEST_SLUG" + env: + EXPECTED_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + EXPECTED_QUEST_SLUG: microsoft-foundry + EXPECTED_WEEK_KEY: ${{ inputs.week_key }} + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + - name: Upload structured quest report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + env: + GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json + with: + if-no-files-found: error + name: weekly-refresh-${{ inputs.week_key }}-microsoft-foundry + path: /tmp/weekly-refresh/microsoft-foundry.json + retention-days: 30.0 + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Discover Microsoft Foundry" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/discover-microsoft-foundry.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + safe_outputs: needs: - activation @@ -994,8 +1346,6 @@ jobs: code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1013,12 +1363,13 @@ jobs: GH_AW_INFO_VERSION: "1.0.55" GH_AW_INFO_AWF_VERSION: "v0.25.58" GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} - name: Download agent output artifact id: download-agent-output continue-on-error: true uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: agent + name: ${{ needs.activation.outputs.artifact_prefix }}agent path: /tmp/gh-aw/ - name: Setup agent output environment variable id: setup-agent-output-env @@ -1045,7 +1396,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,learn.microsoft.com,lfs.github.com,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"discover-microsoft-foundry\"],\"max\":1,\"title_prefix\":\"[discover-microsoft-foundry]\"}}" + GH_AW_SAFE_OUTPUT_JOBS: "{\"emit_weekly_report\":\"\"}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_SAFE_OUTPUTS_STAGED: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/discover-microsoft-foundry.md b/.github/workflows/discover-microsoft-foundry.md index 0c3c34d..aa380c9 100644 --- a/.github/workflows/discover-microsoft-foundry.md +++ b/.github/workflows/discover-microsoft-foundry.md @@ -2,10 +2,33 @@ name: Discover Microsoft Foundry description: Manually audits the Microsoft Foundry quest using read-only approved evidence on: + workflow_call: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string workflow_dispatch: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string permissions: contents: read copilot-requests: write +concurrency: + group: weekly-refresh-${{ inputs.week_key }}-microsoft-foundry + cancel-in-progress: false + job-discriminator: ${{ inputs.week_key }} strict: true engine: id: copilot @@ -15,6 +38,12 @@ network: - learn.microsoft.com imports: - shared/discovery-policy.md + - uses: shared/report-output.md + with: + quest-id: 2 + quest-slug: microsoft-foundry + quest-title: Microsoft Foundry + quest-label: quest/2-microsoft-foundry - ../agents/microsoft-foundry-quest-master.agent.md safe-outputs: activation-comments: false @@ -28,9 +57,9 @@ safe-outputs: timeout-minutes: 20 --- -# Phase 1 Microsoft Foundry Discovery +# Phase 2A Microsoft Foundry Reporting -Run a read-only manual trial for quest slug `microsoft-foundry`. +Run a read-only staged report for quest slug `microsoft-foundry`. ## Execution @@ -42,15 +71,19 @@ Run a read-only manual trial for quest slug `microsoft-foundry`. file and section references. 5. Reconcile both reports. Reject unsupported claims and make uncertainty explicit. -6. Return one concise GitHub-flavored Markdown report. Do not create or update - any persistent resource. +6. Compare against any exact prior finding keys supplied for + `${{ inputs.prior_week_key }}`. +7. Call `emit_weekly_report` exactly once with the structured report. +8. Return one concise GitHub-flavored Markdown summary. Do not create or update + repository content or GitHub issues, comments, branches, or pull requests. ## Final report contract Start nested headings at `###` and include: - `Result: material-change | no-material-change | blocked` -- Trial key `phase1-${{ github.run_id }}` and quest identity +- Week key `${{ inputs.week_key }}`, prior week `${{ inputs.prior_week_key }}`, + and quest identity - Executive summary and current quest status - Official-source facts and deltas with lifecycle, URL, location, observed wording, and fingerprint diff --git a/.github/workflows/shared/discovery-policy.md b/.github/workflows/shared/discovery-policy.md index afe1bd6..201d2f6 100644 --- a/.github/workflows/shared/discovery-policy.md +++ b/.github/workflows/shared/discovery-policy.md @@ -35,10 +35,11 @@ Apply these Phase 1 discovery rules: independently listed in the matching quest entry. - Use GitHub transport domains only for repositories listed in the matching quest entry. Cite the human-facing GitHub URL, not a transport URL. -- Return the report in the final agent response. No safe-output handler is - configured; automatic comments, missing-data issues, missing-tool issues, - incomplete-run issues, and failure issues are disabled. The workflow-level - staged guard must remain enabled. +- Return a concise summary in the final agent response and use only the imported + `emit_weekly_report` safe-output job for the structured run artifact. + Automatic comments, missing-data issues, missing-tool issues, incomplete-run + issues, and failure issues are disabled. The workflow-level staged guard must + remain enabled. - When no material drift is found, return `Result: no-material-change` with the inspected evidence and any coverage gaps. - When a boundary, citation, lifecycle, or source conflict cannot be resolved, diff --git a/.github/workflows/shared/report-output.md b/.github/workflows/shared/report-output.md new file mode 100644 index 0000000..905d505 --- /dev/null +++ b/.github/workflows/shared/report-output.md @@ -0,0 +1,137 @@ +--- +import-schema: + quest-id: + type: number + required: true + quest-slug: + type: string + required: true + quest-title: + type: string + required: true + quest-label: + type: string + required: true +safe-outputs: + jobs: + emit-weekly-report: + description: Materialize one validated staged weekly quest report artifact + runs-on: ubuntu-latest + needs: agent + if: always() + output: The structured weekly quest report artifact was prepared + permissions: + contents: read + inputs: + result: + description: The exact discovery result + required: true + type: choice + options: + - material-change + - no-material-change + - blocked + report_json: + description: A JSON string matching the weekly quest report contract + required: true + type: string + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Materialize structured quest report + env: + EXPECTED_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + EXPECTED_QUEST_SLUG: ${{ github.aw.import-inputs.quest-slug }} + EXPECTED_WEEK_KEY: ${{ inputs.week_key }} + run: | + node scripts/weekly-refresh-report.mjs materialize \ + --agent-output "$GH_AW_AGENT_OUTPUT" \ + --output "/tmp/weekly-refresh/${EXPECTED_QUEST_SLUG}.json" \ + --week "$EXPECTED_WEEK_KEY" \ + --prior-week "$EXPECTED_PRIOR_WEEK_KEY" \ + --quest "$EXPECTED_QUEST_SLUG" + - name: Upload structured quest report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: weekly-refresh-${{ inputs.week_key }}-${{ github.aw.import-inputs.quest-slug }} + path: /tmp/weekly-refresh/${{ github.aw.import-inputs.quest-slug }}.json + if-no-files-found: error + retention-days: 30 +--- + +Apply this staged weekly reporting contract for quest +`${{ github.aw.import-inputs.quest-slug }}`: + +- Quest identity is `${{ github.aw.import-inputs.quest-id }}`, + `${{ github.aw.import-inputs.quest-title }}`, and + `${{ github.aw.import-inputs.quest-label }}`. +- The reporting week is `${{ inputs.week_key }}` and the prior report locator is + `weekly-refresh:${{ inputs.prior_week_key }}:${{ github.aw.import-inputs.quest-slug }}`. +- Compare current allowed-source evidence, current Buildathon content, and any + prior finding keys available from the exact prior locator. Never broaden the + search to unrelated issues or repositories. +- Call `emit_weekly_report` exactly once, including blocked runs. The `result` + argument and `report_json.result` must match. +- Do not write a report file yourself. The deterministic safe-output job validates + the JSON and creates the run-scoped artifact. + +The `report_json` value must encode exactly this version 1 shape: + +```json +{ + "schemaVersion": 1, + "weekKey": "${{ inputs.week_key }}", + "priorWeekKey": "${{ inputs.prior_week_key }}", + "questSlug": "${{ github.aw.import-inputs.quest-slug }}", + "result": "material-change | no-material-change | blocked", + "executiveSummary": "short summary", + "currentQuestStatus": "current status", + "learnerImpact": "high | medium | low | none", + "officialSourceDeltas": [ + { + "fact": "source fact", + "lifecycle": "GA | Public preview | Private preview | Not stated by allowed sources", + "evidenceUrl": "allowlisted URL", + "evidenceLocation": "heading or section", + "observedWording": "short factual wording", + "fingerprint": "source fingerprint" + } + ], + "contentDrift": [ + { + "path": "owned Buildathon path", + "section": "section reference", + "observation": "repository observation" + } + ], + "proposedChanges": [ + { + "repository": "Azure-Samples/JavaScript-AI-Buildathon", + "path": "owned Buildathon path", + "description": "bounded proposed change" + } + ], + "assetImpact": "asset impact or empty string", + "codeTourImpact": [ + { + "repository": "exact external report-only repository", + "path": "exact observed CodeTour path", + "mode": "report-only", + "handoff": "manual-maintainer", + "observation": "bounded observation" + } + ], + "validationPlan": ["validation step"], + "risks": ["risk or uncertainty"], + "humanDecisions": ["decision required from Julia"], + "sourceFingerprints": ["source fingerprint"], + "priorFindingKeys": ["finding:<64 lowercase hex characters>"], + "blockedReason": null +} +``` + +For `material-change`, include at least one proposed Buildathon change and use +`high`, `medium`, or `low` learner impact. For `no-material-change`, leave +`proposedChanges` empty. For `blocked`, set a concise `blockedReason`, leave +`proposedChanges` empty, and use `none` learner impact. diff --git a/.github/workflows/validate-agentic-workflows.yml b/.github/workflows/validate-agentic-workflows.yml index 0a11044..f6ccc65 100644 --- a/.github/workflows/validate-agentic-workflows.yml +++ b/.github/workflows/validate-agentic-workflows.yml @@ -12,7 +12,7 @@ on: - ".github/mcp.json" - ".github/skills/**" - ".github/workflows/**" - - "scripts/validate-refresh-config.mjs" + - "scripts/**" workflow_dispatch: permissions: diff --git a/.github/workflows/validate-content.yml b/.github/workflows/validate-content.yml index 6cd7bf9..b1eb077 100644 --- a/.github/workflows/validate-content.yml +++ b/.github/workflows/validate-content.yml @@ -11,8 +11,7 @@ on: - "docs/**" - "mkdocs.yml" - "requirements.txt" - - "scripts/validate-content-links.mjs" - - "scripts/validate-refresh-config.mjs" + - "scripts/**" - ".github/workflows/validate-content.yml" workflow_dispatch: @@ -35,6 +34,9 @@ jobs: - name: Validate refresh configuration run: node scripts/validate-refresh-config.mjs + - name: Test weekly refresh reporting + run: node scripts/weekly-refresh-report.mjs self-test + - name: Validate local content links run: node scripts/validate-content-links.mjs diff --git a/.github/workflows/weekly-refresh-digest.lock.yml b/.github/workflows/weekly-refresh-digest.lock.yml new file mode 100644 index 0000000..d7644d1 --- /dev/null +++ b/.github/workflows/weekly-refresh-digest.lock.yml @@ -0,0 +1,1174 @@ +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5caa1f9e7525766cb092f962fd6af5f2a54eeabfb70299bb2aaa86b41d4dfcd3","body_hash":"bf8dbc7572167bc9b6249e36fe6df0f6e0cd4afab7d820cc4fecdf1fdce33e7c","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.77.5). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Deterministically assembles staged quest reports into parent and child issue previews +# +# Secrets used: +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.58 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.58 +# - ghcr.io/github/gh-aw-mcpg:v0.3.22 +# - ghcr.io/github/github-mcp-server:v1.1.0 +# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + +name: "Build Weekly Refresh Digest Preview" +on: + workflow_call: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string + week_key: + description: ISO week key for this reporting run + required: true + type: string + outputs: + created_issue_number: + description: Number of the first created issue + value: ${{ jobs.safe_outputs.outputs.created_issue_number }} + created_issue_url: + description: URL of the first created issue + value: ${{ jobs.safe_outputs.outputs.created_issue_url }} + secrets: + GH_AW_GITHUB_MCP_SERVER_TOKEN: + required: false + GH_AW_GITHUB_TOKEN: + required: false + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: "Build Weekly Refresh Digest Preview" + +jobs: + activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + target_checkout_ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} + target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} + target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} + target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-refresh-digest.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Resolve host repo for activation checkout + id: resolve-host-repo + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + JOB_WORKFLOW_REPOSITORY: ${{ job.workflow_repository }} + JOB_WORKFLOW_SHA: ${{ job.workflow_sha }} + JOB_WORKFLOW_REF: ${{ job.workflow_ref }} + JOB_WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/resolve_host_repo.cjs'); + await main(); + - name: Compute artifact prefix + id: artifact-prefix + env: + INPUTS_JSON: ${{ toJSON(inputs) }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AGENT_VERSION: "1.0.55" + GH_AW_INFO_CLI_VERSION: "v0.77.5" + GH_AW_INFO_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "true" + GH_AW_INFO_ALLOWED_DOMAINS: '[]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Print cross-repo setup guidance + if: failure() && steps.resolve-host-repo.outputs.target_repo != github.repository + run: | + echo "::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets." + echo "::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow." + echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup" + - name: Checkout .github and .agents folders + if: steps.resolve-host-repo.outputs.target_repo == github.repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + repository: ${{ steps.resolve-host-repo.outputs.target_repo }} + ref: ${{ steps.resolve-host-repo.outputs.target_checkout_ref }} + sparse-checkout: | + .github + .agents + .antigravity + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "weekly-refresh-digest.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.77.5" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_08c8bbacd97d5fa0_EOF' + + GH_AW_PROMPT_08c8bbacd97d5fa0_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_08c8bbacd97d5fa0_EOF' + + Tools: create_issue + GH_AW_PROMPT_08c8bbacd97d5fa0_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_auto_create_issue.md" + cat << 'GH_AW_PROMPT_08c8bbacd97d5fa0_EOF' + + GH_AW_PROMPT_08c8bbacd97d5fa0_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_08c8bbacd97d5fa0_EOF' + + The following GitHub context information is available for this workflow: + {{#if github.actor}} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if github.repository}} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if github.workspace}} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ + {{/if}} + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ + {{/if}} + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ + {{/if}} + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ + {{/if}} + {{#if github.run_id}} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_08c8bbacd97d5fa0_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_08c8bbacd97d5fa0_EOF' + + {{#runtime-import .github/workflows/weekly-refresh-digest.md}} + GH_AW_PROMPT_08c8bbacd97d5fa0_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_INPUTS_WEEK_KEY: process.env.GH_AW_INPUTS_WEEK_KEY, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ steps.artifact-prefix.outputs.prefix }}activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/model_multipliers.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + copilot-requests: write + concurrency: + group: "gh-aw-copilot-weekly-refresh-digest" + queue: max + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: weeklyrefreshdigest + outputs: + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-refresh-digest.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - continue-on-error: true + name: Download staged quest reports + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + merge-multiple: true + path: /tmp/gh-aw/agent/weekly-refresh-reports + pattern: weekly-refresh-${{ inputs.week_key }}-* + - env: + GH_AW_INPUTS_PRIOR_WEEK_KEY: ${{ inputs.prior_week_key }} + GH_AW_INPUTS_WEEK_KEY: ${{ inputs.week_key }} + name: Build deterministic digest preview + run: | + node scripts/weekly-refresh-report.mjs digest \ + --reports-dir /tmp/gh-aw/agent/weekly-refresh-reports \ + --output-dir /tmp/gh-aw/agent/weekly-refresh-preview \ + --week "$GH_AW_INPUTS_WEEK_KEY" \ + --prior-week "$GH_AW_INPUTS_PRIOR_WEEK_KEY" + - name: Upload staged digest preview + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + if-no-files-found: error + name: weekly-refresh-${{ inputs.week_key }}-preview + path: /tmp/gh-aw/agent/weekly-refresh-preview + retention-days: 30 + + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.55 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.58 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.activation.outputs.artifact_prefix }}activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.58 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 ghcr.io/github/gh-aw-firewall/squid:0.25.58 ghcr.io/github/gh-aw-mcpg:v0.3.22 ghcr.io/github/github-mcp-server:v1.1.0 node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + - name: Generate Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_5ae00f67103541aa_EOF' + {"create_issue":{"labels":["weekly-refresh-digest"],"max":1,"title_prefix":"[weekly-refresh-digest]"}} + GH_AW_SAFE_OUTPUTS_CONFIG_5ae00f67103541aa_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[weekly-refresh-digest]\". Labels [\"weekly-refresh-digest\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "fields": { + "type": "array" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.22' + + mkdir -p /home/runner/.copilot + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_9da31ebd0612ba53_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.1.0", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_9da31ebd0612ba53_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool github + # --allow-tool safeoutputs + # --allow-tool shell(cat /tmp/gh-aw/agent/weekly-refresh-preview/*) + # --allow-tool shell(cat) + # --allow-tool shell(date) + # --allow-tool shell(echo) + # --allow-tool shell(find /tmp/gh-aw/agent/weekly-refresh-preview) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq * /tmp/gh-aw/agent/weekly-refresh-preview/*) + # --allow-tool shell(ls) + # --allow-tool shell(printf) + # --allow-tool shell(pwd) + # --allow-tool shell(safeoutputs:*) + # --allow-tool shell(sort) + # --allow-tool shell(tail) + # --allow-tool shell(uniq) + # --allow-tool shell(wc * /tmp/gh-aw/agent/weekly-refresh-preview/*) + # --allow-tool shell(wc) + # --allow-tool shell(yq) + # --allow-tool write + timeout-minutes: 10 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.58/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","raw.githubusercontent.com","registry.npmjs.org","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.58"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + elif [ -d "/home/runner/work/_tool" ]; then + GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat /tmp/gh-aw/agent/weekly-refresh-preview/*)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find /tmp/gh-aw/agent/weekly-refresh-preview)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq * /tmp/gh-aw/agent/weekly-refresh-preview/*)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc * /tmp/gh-aw/agent/weekly-refresh-preview/*)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_STAGED: true + GH_AW_VERSION: v0.77.5 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + XDG_CONFIG_HOME: /home/runner + - name: Detect agent errors + if: always() + id: detect-agent-errors + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ needs.activation.outputs.artifact_prefix }}agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: {} + concurrency: + group: "gh-aw-conclusion-weekly-refresh-digest" + cancel-in-progress: false + queue: max + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-refresh-digest.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.activation.outputs.artifact_prefix }}agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/weekly-refresh-digest.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "weekly-refresh-digest" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"activationComments\":\"false\"}" + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "false" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "10" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-refresh-digest.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + if: (!cancelled()) && needs.agent.result != 'skipped' + runs-on: ubuntu-slim + permissions: {} + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/weekly-refresh-digest" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.55" + GH_AW_SAFE_OUTPUTS_STAGED: "true" + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"activationComments\":\"false\"}" + GH_AW_WORKFLOW_ID: "weekly-refresh-digest" + GH_AW_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/weekly-refresh-digest.md" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Build Weekly Refresh Digest Preview" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/weekly-refresh-digest.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.55" + GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_SETUP_AW_CONTEXT: ${{ inputs.aw_context }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ needs.activation.outputs.artifact_prefix }}agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"weekly-refresh-digest\"],\"max\":1,\"title_prefix\":\"[weekly-refresh-digest]\"}}" + GH_AW_SAFE_OUTPUTS_STAGED: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + diff --git a/.github/workflows/weekly-refresh-digest.md b/.github/workflows/weekly-refresh-digest.md new file mode 100644 index 0000000..7023037 --- /dev/null +++ b/.github/workflows/weekly-refresh-digest.md @@ -0,0 +1,74 @@ +--- +name: Build Weekly Refresh Digest Preview +description: Deterministically assembles staged quest reports into parent and child issue previews +on: + workflow_call: + inputs: + week_key: + description: ISO week key for this reporting run + required: true + type: string + prior_week_key: + description: ISO week key used to locate prior findings + required: true + type: string +permissions: + actions: read + contents: read + copilot-requests: write +strict: true +network: {} +tools: + bash: + - "cat /tmp/gh-aw/agent/weekly-refresh-preview/*" + - "find /tmp/gh-aw/agent/weekly-refresh-preview *" + - "jq * /tmp/gh-aw/agent/weekly-refresh-preview/*" + - "wc * /tmp/gh-aw/agent/weekly-refresh-preview/*" +steps: + - name: Download staged quest reports + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: weekly-refresh-${{ inputs.week_key }}-* + path: /tmp/gh-aw/agent/weekly-refresh-reports + merge-multiple: true + - name: Build deterministic digest preview + run: | + node scripts/weekly-refresh-report.mjs digest \ + --reports-dir /tmp/gh-aw/agent/weekly-refresh-reports \ + --output-dir /tmp/gh-aw/agent/weekly-refresh-preview \ + --week "${{ inputs.week_key }}" \ + --prior-week "${{ inputs.prior_week_key }}" + - name: Upload staged digest preview + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: weekly-refresh-${{ inputs.week_key }}-preview + path: /tmp/gh-aw/agent/weekly-refresh-preview + if-no-files-found: error + retention-days: 30 +safe-outputs: + activation-comments: false + missing-data: false + missing-tool: false + noop: false + report-failure-as-issue: false + report-incomplete: false + staged: true + threat-detection: false +timeout-minutes: 10 +--- + +# Phase 2A Weekly Refresh Digest Audit + +Audit the deterministic preview for `${{ inputs.week_key }}` without changing it. + +1. Read `/tmp/gh-aw/agent/weekly-refresh-preview/digest.json`. +2. Confirm it contains exactly five quest results and that every result is + `material-change`, `no-material-change`, or `blocked`. +3. Confirm `parent-issue.md` covers all five quests. +4. Confirm a child preview exists only for each material-change result. +5. Report any missing, malformed, or inconsistent preview as blocked. + +Return a concise GitHub-flavored Markdown audit starting nested headings at +`###`. Do not create or update issues, comments, branches, pull requests, +workflows, or repository content. diff --git a/.github/workflows/weekly-refresh-orchestrator.yml b/.github/workflows/weekly-refresh-orchestrator.yml new file mode 100644 index 0000000..7fe6dba --- /dev/null +++ b/.github/workflows/weekly-refresh-orchestrator.yml @@ -0,0 +1,86 @@ +name: Weekly Refresh Preview + +# Phase 2B may add `0 8 * * 1` only after default-branch trials and issue-output +# spikes pass. Phase 2A remains manual-only. +on: + workflow_dispatch: + inputs: + week_key: + description: Optional ISO week key; defaults to the current UTC ISO week + required: false + type: string + +permissions: + actions: read + contents: read + copilot-requests: write + +jobs: + context: + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + week_key: ${{ steps.context.outputs.week_key }} + prior_week_key: ${{ steps.context.outputs.prior_week_key }} + parent_key: ${{ steps.context.outputs.parent_key }} + steps: + - name: Checkout repository + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - name: Resolve deterministic reporting context + id: context + env: + REQUESTED_WEEK_KEY: ${{ inputs.week_key }} + run: | + node scripts/weekly-refresh-report.mjs context \ + --week "$REQUESTED_WEEK_KEY" \ + --github-output "$GITHUB_OUTPUT" + + foundry-local: + needs: context + uses: ./.github/workflows/discover-foundry-local.lock.yml + with: + week_key: ${{ needs.context.outputs.week_key }} + prior_week_key: ${{ needs.context.outputs.prior_week_key }} + + microsoft-foundry: + needs: context + uses: ./.github/workflows/discover-microsoft-foundry.lock.yml + with: + week_key: ${{ needs.context.outputs.week_key }} + prior_week_key: ${{ needs.context.outputs.prior_week_key }} + + agentic-rag: + needs: context + uses: ./.github/workflows/discover-agentic-rag.lock.yml + with: + week_key: ${{ needs.context.outputs.week_key }} + prior_week_key: ${{ needs.context.outputs.prior_week_key }} + + foundry-toolkit: + needs: context + uses: ./.github/workflows/discover-foundry-toolkit.lock.yml + with: + week_key: ${{ needs.context.outputs.week_key }} + prior_week_key: ${{ needs.context.outputs.prior_week_key }} + + context-engineering: + needs: context + uses: ./.github/workflows/discover-context-engineering.lock.yml + with: + week_key: ${{ needs.context.outputs.week_key }} + prior_week_key: ${{ needs.context.outputs.prior_week_key }} + + digest: + if: ${{ always() && needs.context.result == 'success' }} + needs: + - context + - foundry-local + - microsoft-foundry + - agentic-rag + - foundry-toolkit + - context-engineering + uses: ./.github/workflows/weekly-refresh-digest.lock.yml + with: + week_key: ${{ needs.context.outputs.week_key }} + prior_week_key: ${{ needs.context.outputs.prior_week_key }} diff --git a/scripts/validate-refresh-config.mjs b/scripts/validate-refresh-config.mjs index e14c33e..d9ad169 100644 --- a/scripts/validate-refresh-config.mjs +++ b/scripts/validate-refresh-config.mjs @@ -4,6 +4,11 @@ import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const errors = []; +const resultValues = [ + "material-change", + "no-material-change", + "blocked", +]; function check(condition, message) { if (!condition) { @@ -802,10 +807,10 @@ function validateDiscoveryPolicy(policyText) { "shared discovery policy must leave output suppression to each main workflow", ); check( - policyText.includes("No safe-output handler") && + policyText.includes("`emit_weekly_report`") && policyText.includes("Result: no-material-change") && policyText.includes("Result: blocked"), - "discovery policy must define explicit no-change and blocked outcomes", + "discovery policy must define the staged report output and explicit outcomes", ); } @@ -833,9 +838,10 @@ function validateDiscoveryWorkflow( ]; check( - /^\s{2}workflow_dispatch:\s*$/m.test(frontmatter) && + /^\s{2}workflow_call:\s*$/m.test(frontmatter) && + /^\s{2}workflow_dispatch:\s*$/m.test(frontmatter) && !/^\s{2}schedule:\s*$/m.test(frontmatter), - `${workflowPath} must be manual-only in Phase 1`, + `${workflowPath} must support manual and reusable Phase 2A runs without a schedule`, ); check( /^strict:\s*true$/m.test(frontmatter), @@ -853,8 +859,13 @@ function validateDiscoveryWorkflow( ); check( frontmatter.includes("shared/discovery-policy.md") && + frontmatter.includes("uses: shared/report-output.md") && + frontmatter.includes(`quest-id: ${quest.id}`) && + frontmatter.includes(`quest-slug: ${quest.slug}`) && + frontmatter.includes(`quest-title: ${quest.title}`) && + frontmatter.includes(`quest-label: quest/${quest.id}-${quest.slug}`) && frontmatter.includes(`../agents/${quest.agent}.agent.md`), - `${workflowPath} must import the shared policy and exactly scoped Quest Master`, + `${workflowPath} must import the reporting policy and exactly scoped Quest Master`, ); check( sameJson(allowedDomains, specification.domains), @@ -870,13 +881,14 @@ function validateDiscoveryWorkflow( !/^\s{2}(?:contents|issues|pull-requests|actions):\s*write$/m.test( frontmatter, ), - `${workflowPath} must disable automatic writes without enabling mutation permissions or outputs`, + `${workflowPath} must disable automatic writes and retain staged output processing`, ); check( compiledText.includes('GH_AW_FAILURE_REPORT_AS_ISSUE: "false"') && compiledText.includes('GH_AW_SAFE_OUTPUTS_STAGED: "true"') && - compiledText.includes("Tools: create_issue"), - `${workflowPath} compiled fallback must remain disabled and staged`, + compiledText.includes("emit_weekly_report") && + compiledText.includes("weekly-refresh-report.mjs materialize"), + `${workflowPath} compiled reporting output must remain disabled for writes and staged`, ); check( !/^\s{6}(?:contents|issues|pull-requests|actions|checks):\s*write$/m.test( @@ -890,8 +902,11 @@ function validateDiscoveryWorkflow( ); check( workflowText.includes(`quest slug \`${quest.slug}\``) && + workflowText.includes("${{ inputs.week_key }}") && + workflowText.includes("${{ inputs.prior_week_key }}") && + workflowText.includes("`emit_weekly_report`") && workflowText.includes("Do not create or update"), - `${workflowPath} must declare its fixed quest and read-only outcome`, + `${workflowPath} must declare its fixed quest, reporting context, and read-only outcome`, ); check( new RegExp(`^name:\\s*${quest.agent}$`, "m").test(agentFrontmatter) && @@ -930,6 +945,169 @@ function validateDiscoveryWorkflow( } } +function validateReportOutput(reportOutputText, actionsLock) { + const path = ".github/workflows/shared/report-output.md"; + const frontmatter = readFrontmatter(reportOutputText, path); + const checkoutPin = + actionsLock.entries["actions/checkout@v6.0.2"]?.sha; + const uploadPin = + actionsLock.entries["actions/upload-artifact@v7.0.1"]?.sha; + + check( + ["quest-id", "quest-slug", "quest-title", "quest-label"].every((field) => + new RegExp(`^ ${field}:\\s*$`, "m").test(frontmatter), + ), + `${path} must define the exact parameterized quest identity`, + ); + check( + /^\s{4}emit-weekly-report:\s*$/m.test(frontmatter) && + frontmatter.includes("needs: agent") && + frontmatter.includes("if: always()") && + frontmatter.includes("type: choice") && + frontmatter.includes("report_json:"), + `${path} must define one structured report materializer job`, + ); + check( + frontmatter.includes( + `actions/checkout@${checkoutPin}`, + ) && + frontmatter.includes( + `actions/upload-artifact@${uploadPin}`, + ) && + frontmatter.includes("weekly-refresh-report.mjs materialize") && + frontmatter.includes('"$GH_AW_AGENT_OUTPUT"'), + `${path} must materialize and upload reports with pinned actions`, + ); + check( + !/^\s+(?:contents|issues|pull-requests|actions|checks|statuses):\s*write$/m.test( + frontmatter, + ), + `${path} must not grant a repository write permission`, + ); + check( + reportOutputText.includes("schemaVersion") && + reportOutputText.includes("officialSourceDeltas") && + reportOutputText.includes("priorFindingKeys") && + reportOutputText.includes("blockedReason") && + reportOutputText.includes("Call `emit_weekly_report` exactly once"), + `${path} must expose the complete versioned report contract`, + ); + validatePinnedActions(reportOutputText, path); +} + +function validateDigestWorkflow(digestText, compiledText, tooling) { + const path = ".github/workflows/weekly-refresh-digest.md"; + const frontmatter = readFrontmatter(digestText, path); + + check( + /^\s{2}workflow_call:\s*$/m.test(frontmatter) && + !/^\s{2}workflow_dispatch:\s*$/m.test(frontmatter) && + !/^\s{2}schedule:\s*$/m.test(frontmatter), + `${path} must be reusable only; the orchestrator owns manual activation`, + ); + check( + /^permissions:\s*\n actions:\s*read\s*\n contents:\s*read\s*\n copilot-requests:\s*write$/m.test( + frontmatter, + ), + `${path} must grant only artifact, repository-read, and inference permissions`, + ); + check( + /^network:\s*\{\}$/m.test(frontmatter), + `${path} must deny agent network access`, + ); + check( + frontmatter.includes(tooling.downloadArtifactAction) && + frontmatter.includes(tooling.uploadArtifactAction) && + frontmatter.includes("weekly-refresh-report.mjs digest") && + frontmatter.includes("continue-on-error: true") && + frontmatter.includes("merge-multiple: true"), + `${path} must tolerate missing quest artifacts and build a pinned deterministic preview`, + ); + check( + /^safe-outputs:\s*\n activation-comments:\s*false\s*\n missing-data:\s*false\s*\n missing-tool:\s*false\s*\n noop:\s*false\s*\n report-failure-as-issue:\s*false\s*\n report-incomplete:\s*false\s*\n staged:\s*true\s*\n threat-detection:\s*false$/m.test( + frontmatter, + ), + `${path} must keep every framework output staged and suppressed`, + ); + check( + compiledText.includes('GH_AW_SAFE_OUTPUTS_STAGED: "true"') && + compiledText.includes("weekly-refresh-report.mjs digest") && + !/^\s+(?:contents|issues|pull-requests|actions|checks|statuses):\s*write$/m.test( + compiledText, + ), + `${path} compiled workflow must remain staged and free of repository writes`, + ); + check( + digestText.includes("exactly five quest results") && + digestText.includes("child preview exists only") && + digestText.includes("Do not create or update"), + `${path} must audit the deterministic five-quest preview without mutation`, + ); + validatePinnedActions(digestText, path); +} + +function validateOrchestrator(orchestratorText, quests, tooling) { + const path = ".github/workflows/weekly-refresh-orchestrator.yml"; + const discoveryUses = [ + ...orchestratorText.matchAll( + /uses:\s*\.\/\.github\/workflows\/(discover-[a-z-]+\.lock\.yml)/g, + ), + ].map((match) => match[1]); + const expectedUses = quests.quests + .map(({ slug }) => `discover-${slug}.lock.yml`) + .sort(); + + check( + /^\s{2}workflow_dispatch:\s*$/m.test(orchestratorText) && + !/^\s{2}schedule:\s*$/m.test(orchestratorText), + `${path} must remain manual-only in Phase 2A`, + ); + check( + orchestratorText.includes("0 8 * * 1") && + orchestratorText.includes("Phase 2B"), + `${path} must document but not activate the intended Monday schedule`, + ); + check( + /^permissions:\s*\n actions:\s*read\s*\n contents:\s*read\s*\n copilot-requests:\s*write$/m.test( + orchestratorText, + ) && + !/^\s+(?:contents|issues|pull-requests|actions|checks|statuses):\s*write$/m.test( + orchestratorText, + ), + `${path} must grant only read and inference permissions`, + ); + check( + sameJson(discoveryUses.sort(), expectedUses) && + discoveryUses.length === quests.quests.length, + `${path} must fan out to each discovery workflow exactly once`, + ); + check( + orchestratorText.includes("weekly-refresh-report.mjs context") && + orchestratorText.includes("prior_week_key") && + orchestratorText.includes("always() && needs.context.result == 'success'") && + orchestratorText.includes( + "uses: ./.github/workflows/weekly-refresh-digest.lock.yml", + ), + `${path} must use one week algorithm and run the digest after every quest outcome`, + ); + check( + orchestratorText.includes(tooling.checkoutAction), + `${path} must use the reviewed checkout action pin`, + ); + validatePinnedActions(orchestratorText, path); +} + +function validateReportContract(reportContractText) { + check( + reportContractText.includes("version 1 JSON artifact") && + resultValues.every((result) => reportContractText.includes(result)) && + reportContractText.includes("deterministic parent key") && + reportContractText.includes("child issue Markdown preview only") && + reportContractText.includes("No Phase 2A workflow creates or updates"), + "report-contract.md must define the Phase 2A schema, outcomes, identifiers, and no-write boundary", + ); +} + const capabilities = await readJson(".github/agentic-refresh/capabilities.json"); const quests = await readJson(".github/agentic-refresh/quests.json"); const labels = await readJson(".github/agentic-refresh/labels.json"); @@ -941,6 +1119,7 @@ const humanMergeRuleset = await readJson( ".github/agentic-refresh/rulesets/human-merge.json", ); const tooling = await readJson(".github/agentic-refresh/tooling.json"); +const actionsLock = await readJson(".github/aw/actions-lock.json"); const codeowners = await readText(".github/CODEOWNERS"); const copilotSetup = await readText( ".github/workflows/copilot-setup-steps.yml", @@ -952,6 +1131,21 @@ const workflowFiles = (await readdir(resolve(root, workflowDirectory))) const discoveryPolicy = await readText( ".github/workflows/shared/discovery-policy.md", ); +const reportOutput = await readText( + ".github/workflows/shared/report-output.md", +); +const reportContract = await readText( + ".github/agentic-refresh/report-contract.md", +); +const digestWorkflow = await readText( + ".github/workflows/weekly-refresh-digest.md", +); +const digestCompiledWorkflow = await readText( + ".github/workflows/weekly-refresh-digest.lock.yml", +); +const weeklyOrchestrator = await readText( + ".github/workflows/weekly-refresh-orchestrator.yml", +); const discoverySpecifications = new Map([ [ "foundry-local", @@ -1014,6 +1208,10 @@ validateGitHubTokenPolicy(tokenPolicy); validateRuleset(ruleset); validateHumanMergeRuleset(humanMergeRuleset); validateDiscoveryPolicy(discoveryPolicy); +validateReportOutput(reportOutput, actionsLock); +validateDigestWorkflow(digestWorkflow, digestCompiledWorkflow, tooling); +validateOrchestrator(weeklyOrchestrator, quests, tooling); +validateReportContract(reportContract); for (const quest of quests.quests) { const specification = discoverySpecifications.get(quest.slug); @@ -1036,6 +1234,13 @@ check( tooling.ghAwVersion === "v0.77.5", "tooling.json must pin gh-aw v0.77.5", ); +check( + tooling.downloadArtifactAction === + `actions/download-artifact@${actionsLock.entries["actions/download-artifact@v8.0.1"]?.sha}` && + tooling.uploadArtifactAction === + `actions/upload-artifact@${actionsLock.entries["actions/upload-artifact@v7.0.1"]?.sha}`, + "tooling.json artifact action pins must match the gh-aw action lock", +); check( copilotSetup.includes(tooling.ghAwSetupAction) && copilotSetup.includes(`version: ${tooling.ghAwVersion}`), @@ -1061,6 +1266,6 @@ if (errors.length > 0) { process.exitCode = 1; } else { console.log( - `Validated ${quests.quests.length} quests, ${discoverySpecifications.size} read-only discovery workflows, and ${capabilities.repositories.length} repository capability records.`, + `Validated ${quests.quests.length} quests, ${discoverySpecifications.size} staged reporting workflows, the weekly digest/orchestrator, and ${capabilities.repositories.length} repository capability records.`, ); } diff --git a/scripts/weekly-refresh-report.mjs b/scripts/weekly-refresh-report.mjs new file mode 100644 index 0000000..a2c6440 --- /dev/null +++ b/scripts/weekly-refresh-report.mjs @@ -0,0 +1,1069 @@ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { + mkdir, + mkdtemp, + readFile, + rm, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, posix, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const resultValues = [ + "material-change", + "no-material-change", + "blocked", +]; +const lifecycleValues = [ + "GA", + "Public preview", + "Private preview", + "Not stated by allowed sources", +]; +const learnerImpactValues = ["high", "medium", "low", "none"]; +const coreKeys = [ + "schemaVersion", + "weekKey", + "priorWeekKey", + "questSlug", + "result", + "executiveSummary", + "currentQuestStatus", + "learnerImpact", + "officialSourceDeltas", + "contentDrift", + "proposedChanges", + "assetImpact", + "codeTourImpact", + "validationPlan", + "risks", + "humanDecisions", + "sourceFingerprints", + "priorFindingKeys", + "blockedReason", +]; + +function parseArguments(argv) { + const [command, ...values] = argv; + const options = {}; + + for (let index = 0; index < values.length; index += 2) { + const flag = values[index]; + const value = values[index + 1]; + if (!flag?.startsWith("--") || value === undefined) { + throw new Error(`Invalid argument sequence near ${flag ?? ""}`); + } + options[flag.slice(2)] = value; + } + + return { command, options }; +} + +function canonicalize(value) { + if (Array.isArray(value)) { + return value.map(canonicalize); + } + if (value !== null && typeof value === "object") { + return Object.fromEntries( + Object.entries(value) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, nestedValue]) => [key, canonicalize(nestedValue)]), + ); + } + return value; +} + +function stableJson(value) { + return JSON.stringify(canonicalize(value)); +} + +function sha256(value) { + return createHash("sha256").update(stableJson(value)).digest("hex"); +} + +function uniqueSorted(values) { + return [...new Set(values)].sort(); +} + +function isoWeekKey(date) { + const target = new Date( + Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()), + ); + const day = target.getUTCDay() || 7; + target.setUTCDate(target.getUTCDate() + 4 - day); + const isoYear = target.getUTCFullYear(); + const yearStart = new Date(Date.UTC(isoYear, 0, 1)); + const week = Math.ceil( + ((target.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7, + ); + return `${isoYear}-W${String(week).padStart(2, "0")}`; +} + +function mondayForWeekKey(weekKey) { + const match = /^([0-9]{4})-W([0-9]{2})$/.exec(weekKey); + if (!match) { + throw new Error(`Invalid ISO week key: ${weekKey}`); + } + + const year = Number(match[1]); + const week = Number(match[2]); + const januaryFourth = new Date(Date.UTC(year, 0, 4)); + const januaryFourthDay = januaryFourth.getUTCDay() || 7; + const monday = new Date(januaryFourth); + monday.setUTCDate(januaryFourth.getUTCDate() - januaryFourthDay + 1 + (week - 1) * 7); + + if (isoWeekKey(monday) !== weekKey) { + throw new Error(`ISO week does not exist: ${weekKey}`); + } + return monday; +} + +function weekContext(requestedWeek) { + const weekKey = requestedWeek || isoWeekKey(new Date()); + const monday = mondayForWeekKey(weekKey); + const priorMonday = new Date(monday); + priorMonday.setUTCDate(priorMonday.getUTCDate() - 7); + return { + weekKey, + priorWeekKey: isoWeekKey(priorMonday), + parentKey: `weekly-refresh:${weekKey}`, + parentMarker: ``, + }; +} + +async function readQuests() { + const text = await readFile( + resolve(root, ".github/agentic-refresh/quests.json"), + "utf8", + ); + return JSON.parse(text); +} + +function questLabel(quest) { + return `quest/${quest.id}-${quest.slug}`; +} + +function isOwnedPath(quest, repository, path) { + if ( + typeof path !== "string" || + path.length === 0 || + path.startsWith("/") || + path.includes("\\") || + posix.normalize(path) !== path || + path.split("/").some((segment) => segment === "." || segment === "..") + ) { + return false; + } + const patterns = quest.ownedPaths?.[repository] ?? []; + return patterns.some((pattern) => { + if (pattern.endsWith("/**")) { + const directory = pattern.slice(0, -3); + return path === directory || path.startsWith(`${directory}/`); + } + return path === pattern; + }); +} + +function isAllowedSource(quest, url) { + let candidate; + try { + candidate = new URL(url); + } catch { + return false; + } + + return quest.officialSourcePrefixes.some((prefix) => { + const allowed = new URL(prefix); + const allowedPath = allowed.pathname.replace(/\/+$/, ""); + return ( + candidate.origin === allowed.origin && + (candidate.pathname === allowedPath || + candidate.pathname.startsWith(`${allowedPath}/`)) + ); + }); +} + +function externalPathAllowed(quest, repository, path) { + return quest.externalReports.some( + (report) => + report.repository === repository && report.observedPaths.includes(path), + ); +} + +function requireString(errors, value, label, { allowEmpty = false, max = 8_000 } = {}) { + if ( + typeof value !== "string" || + (!allowEmpty && value.trim().length === 0) || + value.length > max + ) { + errors.push(`${label} must be a ${allowEmpty ? "" : "non-empty "}string of at most ${max} characters`); + } +} + +function requireStringArray(errors, value, label, { maxItems = 50 } = {}) { + if (!Array.isArray(value) || value.length > maxItems) { + errors.push(`${label} must be an array with at most ${maxItems} items`); + return; + } + value.forEach((item, index) => + requireString(errors, item, `${label}[${index}]`, { max: 2_000 }), + ); + if (new Set(value).size !== value.length) { + errors.push(`${label} must not contain duplicates`); + } +} + +function validateCoreReport(report, quest, expected) { + const errors = []; + if (report === null || typeof report !== "object" || Array.isArray(report)) { + return ["report must be a JSON object"]; + } + + const unknownKeys = Object.keys(report).filter((key) => !coreKeys.includes(key)); + const missingKeys = coreKeys.filter((key) => !(key in report)); + if (unknownKeys.length > 0) { + errors.push(`report has unknown fields: ${unknownKeys.join(", ")}`); + } + if (missingKeys.length > 0) { + errors.push(`report is missing fields: ${missingKeys.join(", ")}`); + } + + if (report.schemaVersion !== 1) { + errors.push("schemaVersion must be 1"); + } + if (report.weekKey !== expected.weekKey) { + errors.push(`weekKey must be ${expected.weekKey}`); + } + if (report.priorWeekKey !== expected.priorWeekKey) { + errors.push(`priorWeekKey must be ${expected.priorWeekKey}`); + } + if (report.questSlug !== quest.slug) { + errors.push(`questSlug must be ${quest.slug}`); + } + if (!resultValues.includes(report.result)) { + errors.push(`result must be one of ${resultValues.join(", ")}`); + } + if (!learnerImpactValues.includes(report.learnerImpact)) { + errors.push(`learnerImpact must be one of ${learnerImpactValues.join(", ")}`); + } + + requireString(errors, report.executiveSummary, "executiveSummary", { max: 2_000 }); + requireString(errors, report.currentQuestStatus, "currentQuestStatus", { max: 4_000 }); + requireString(errors, report.assetImpact, "assetImpact", { + allowEmpty: true, + max: 4_000, + }); + requireStringArray(errors, report.validationPlan, "validationPlan"); + requireStringArray(errors, report.risks, "risks"); + requireStringArray(errors, report.humanDecisions, "humanDecisions"); + requireStringArray(errors, report.sourceFingerprints, "sourceFingerprints"); + requireStringArray(errors, report.priorFindingKeys, "priorFindingKeys"); + + for (const [index, key] of (report.priorFindingKeys ?? []).entries()) { + if (!/^finding:[0-9a-f]{64}$/.test(key)) { + errors.push(`priorFindingKeys[${index}] must be a deterministic finding key`); + } + } + + if (!Array.isArray(report.officialSourceDeltas)) { + errors.push("officialSourceDeltas must be an array"); + } else { + report.officialSourceDeltas.forEach((delta, index) => { + const prefix = `officialSourceDeltas[${index}]`; + if (delta === null || typeof delta !== "object" || Array.isArray(delta)) { + errors.push(`${prefix} must be an object`); + return; + } + for (const field of [ + "fact", + "lifecycle", + "evidenceUrl", + "evidenceLocation", + "observedWording", + "fingerprint", + ]) { + requireString(errors, delta[field], `${prefix}.${field}`, { max: 4_000 }); + } + if (!lifecycleValues.includes(delta.lifecycle)) { + errors.push(`${prefix}.lifecycle is not an allowed lifecycle value`); + } + if (!isAllowedSource(quest, delta.evidenceUrl ?? "")) { + errors.push(`${prefix}.evidenceUrl is outside the quest source allowlist`); + } + if (!report.sourceFingerprints?.includes(delta.fingerprint)) { + errors.push(`${prefix}.fingerprint must appear in sourceFingerprints`); + } + }); + } + + if (!Array.isArray(report.contentDrift)) { + errors.push("contentDrift must be an array"); + } else { + report.contentDrift.forEach((drift, index) => { + const prefix = `contentDrift[${index}]`; + if (drift === null || typeof drift !== "object" || Array.isArray(drift)) { + errors.push(`${prefix} must be an object`); + return; + } + requireString(errors, drift.path, `${prefix}.path`); + requireString(errors, drift.section, `${prefix}.section`, { max: 1_000 }); + requireString(errors, drift.observation, `${prefix}.observation`, { max: 4_000 }); + if ( + !isOwnedPath( + quest, + "Azure-Samples/JavaScript-AI-Buildathon", + drift.path ?? "", + ) + ) { + errors.push(`${prefix}.path is outside the quest ownership boundary`); + } + }); + } + + if (!Array.isArray(report.proposedChanges)) { + errors.push("proposedChanges must be an array"); + } else { + report.proposedChanges.forEach((change, index) => { + const prefix = `proposedChanges[${index}]`; + if (change === null || typeof change !== "object" || Array.isArray(change)) { + errors.push(`${prefix} must be an object`); + return; + } + requireString(errors, change.repository, `${prefix}.repository`); + requireString(errors, change.path, `${prefix}.path`); + requireString(errors, change.description, `${prefix}.description`, { + max: 4_000, + }); + if ( + change.repository !== "Azure-Samples/JavaScript-AI-Buildathon" || + !isOwnedPath(quest, change.repository, change.path ?? "") + ) { + errors.push(`${prefix} must target a Buildathon path owned by this quest`); + } + }); + } + + if (!Array.isArray(report.codeTourImpact)) { + errors.push("codeTourImpact must be an array"); + } else { + report.codeTourImpact.forEach((impact, index) => { + const prefix = `codeTourImpact[${index}]`; + if (impact === null || typeof impact !== "object" || Array.isArray(impact)) { + errors.push(`${prefix} must be an object`); + return; + } + for (const field of ["repository", "path", "mode", "handoff", "observation"]) { + requireString(errors, impact[field], `${prefix}.${field}`, { max: 4_000 }); + } + if ( + impact.mode !== "report-only" || + impact.handoff !== "manual-maintainer" || + !externalPathAllowed(quest, impact.repository ?? "", impact.path ?? "") + ) { + errors.push(`${prefix} must preserve an exact report-only CodeTour handoff`); + } + }); + } + + if (report.result === "material-change") { + if (report.learnerImpact === "none") { + errors.push("material-change must declare high, medium, or low learnerImpact"); + } + if ((report.proposedChanges ?? []).length === 0) { + errors.push("material-change must include at least one proposed change"); + } + if ( + (report.officialSourceDeltas ?? []).length === 0 && + (report.contentDrift ?? []).length === 0 + ) { + errors.push("material-change must include official evidence or content drift"); + } + if (report.blockedReason !== null) { + errors.push("material-change must set blockedReason to null"); + } + } else if (report.result === "no-material-change") { + if ((report.proposedChanges ?? []).length !== 0) { + errors.push("no-material-change must not propose changes"); + } + if (report.blockedReason !== null) { + errors.push("no-material-change must set blockedReason to null"); + } + } else if (report.result === "blocked") { + requireString(errors, report.blockedReason, "blockedReason", { max: 2_000 }); + if ((report.proposedChanges ?? []).length !== 0) { + errors.push("blocked reports must not propose changes"); + } + } + + if ( + report.result !== "blocked" && + (report.sourceFingerprints ?? []).length === 0 + ) { + errors.push("completed reports must include at least one source fingerprint"); + } + + if (Buffer.byteLength(JSON.stringify(report), "utf8") > 262_144) { + errors.push("report exceeds the 256 KiB limit"); + } + return errors; +} + +function findingKeys(report) { + const findings = [ + ...report.officialSourceDeltas.map((delta) => ({ + type: "official-source", + evidenceUrl: delta.evidenceUrl, + evidenceLocation: delta.evidenceLocation, + observedWording: delta.observedWording, + })), + ...report.contentDrift.map((drift) => ({ + type: "content-drift", + path: drift.path, + section: drift.section, + observation: drift.observation, + })), + ...report.codeTourImpact.map((impact) => ({ + type: "codetour", + repository: impact.repository, + path: impact.path, + observation: impact.observation, + })), + ]; + return uniqueSorted( + findings.map((finding) => `finding:${sha256({ + questSlug: report.questSlug, + ...finding, + })}`), + ); +} + +function escapeTable(value) { + return String(value).replaceAll("|", "\\|").replaceAll("\n", " "); +} + +function renderList(values) { + return values.length > 0 ? values.map((value) => `- ${value}`).join("\n") : "- None."; +} + +function renderDeltaTable(deltas) { + if (deltas.length === 0) { + return "No official-source delta was recorded."; + } + return [ + "| Fact | Lifecycle | Evidence | Location | Fingerprint |", + "|---|---|---|---|---|", + ...deltas.map( + (delta) => + `| ${escapeTable(delta.fact)} | ${escapeTable(delta.lifecycle)} | ${escapeTable(delta.evidenceUrl)} | ${escapeTable(delta.evidenceLocation)} | ${escapeTable(delta.fingerprint)} |`, + ), + ].join("\n"); +} + +function buildChildIssuePreview(report, quest, deduplication) { + if (report.result !== "material-change") { + return null; + } + + const manifest = { + version: 1, + weekKey: report.weekKey, + priorWeekKey: report.priorWeekKey, + questSlug: quest.slug, + result: report.result, + parentKey: deduplication.parentKey, + childKey: deduplication.childKey, + sourceFingerprints: report.sourceFingerprints, + findingKeys: deduplication.findingKeys, + priorFindingKeys: report.priorFindingKeys, + }; + const body = [ + deduplication.childMarker, + ``, + "", + "### Executive summary", + report.executiveSummary, + "", + "### Current quest status", + report.currentQuestStatus, + "", + "### Official-source delta", + renderDeltaTable(report.officialSourceDeltas), + "", + "### Learner impact", + report.learnerImpact, + "", + "### Current-content drift", + report.contentDrift.length > 0 + ? report.contentDrift + .map( + (drift) => + `- \`${drift.path}\` — **${drift.section}**: ${drift.observation}`, + ) + .join("\n") + : "- None.", + "", + "### Proposed changes", + report.proposedChanges + .map( + (change) => + `- \`${change.repository}:${change.path}\` — ${change.description}`, + ) + .join("\n"), + "", + "### Asset impact", + report.assetImpact || "None.", + "", + "### CodeTour impact", + report.codeTourImpact.length > 0 + ? report.codeTourImpact + .map( + (impact) => + `- \`${impact.repository}:${impact.path}\` — ${impact.observation} (${impact.mode}, ${impact.handoff})`, + ) + .join("\n") + : "- None.", + "", + "### Stack map", + `- \`refresh/${report.weekKey}/${quest.slug}/01-core\``, + `- \`refresh/${report.weekKey}/${quest.slug}/02-docs\` when documentation synchronization is required`, + "", + "### Validation plan", + renderList(report.validationPlan), + "", + "### Risks and uncertainty", + renderList(report.risks), + "", + "### Human decisions", + renderList(report.humanDecisions), + "", + "### Approval", + "Julia may approve this exact plan by commenting `/approve-refresh`.", + ].join("\n"); + + return { + title: `[Refresh plan][${report.weekKey}] ${quest.title}`, + labels: ["refresh/plan", "refresh/awaiting-approval", questLabel(quest)], + body, + }; +} + +function addDerivedFields(report, quest) { + const normalizedReport = { + ...report, + sourceFingerprints: uniqueSorted(report.sourceFingerprints), + priorFindingKeys: uniqueSorted(report.priorFindingKeys), + }; + const currentFindingKeys = findingKeys(normalizedReport); + const deduplication = { + parentKey: `weekly-refresh:${normalizedReport.weekKey}`, + childKey: `weekly-refresh:${normalizedReport.weekKey}:${quest.slug}`, + parentMarker: ``, + childMarker: ``, + findingKeys: currentFindingKeys, + carriedFindingKeys: currentFindingKeys.filter((key) => + normalizedReport.priorFindingKeys.includes(key), + ), + }; + + return { + ...normalizedReport, + deduplication, + childIssuePreview: buildChildIssuePreview( + normalizedReport, + quest, + deduplication, + ), + }; +} + +function blockedCoreReport(quest, expected, reason) { + return { + schemaVersion: 1, + weekKey: expected.weekKey, + priorWeekKey: expected.priorWeekKey, + questSlug: quest.slug, + result: "blocked", + executiveSummary: "The discovery run did not produce a valid structured report.", + currentQuestStatus: "The quest status could not be assessed safely.", + learnerImpact: "none", + officialSourceDeltas: [], + contentDrift: [], + proposedChanges: [], + assetImpact: "", + codeTourImpact: [], + validationPlan: [], + risks: ["The weekly digest has incomplete evidence for this quest."], + humanDecisions: ["Review the blocked reason before retrying discovery."], + sourceFingerprints: [], + priorFindingKeys: [], + blockedReason: reason.slice(0, 2_000), + }; +} + +async function writeJson(path, value) { + await mkdir(dirname(path), { recursive: true }); + await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); +} + +async function materialize(options) { + const quests = await readQuests(); + const quest = quests.quests.find(({ slug }) => slug === options.quest); + if (!quest) { + throw new Error(`Unknown quest slug: ${options.quest}`); + } + const expected = { + weekKey: options.week, + priorWeekKey: options["prior-week"], + }; + const context = weekContext(expected.weekKey); + if (expected.priorWeekKey !== context.priorWeekKey) { + throw new Error( + `prior week must be ${context.priorWeekKey} for ${expected.weekKey}`, + ); + } + + let artifact; + let failure; + try { + const agentOutput = JSON.parse(await readFile(options["agent-output"], "utf8")); + const items = (agentOutput.items ?? []).filter( + ({ type }) => type === "emit_weekly_report", + ); + if (items.length !== 1) { + throw new Error(`Expected one emit_weekly_report item, received ${items.length}`); + } + const item = items[0]; + const report = JSON.parse(item.report_json); + if (item.result !== report.result) { + throw new Error("Safe-output result does not match report_json.result"); + } + const errors = validateCoreReport(report, quest, expected); + if (errors.length > 0) { + throw new Error(errors.join("; ")); + } + artifact = addDerivedFields(report, quest); + } catch (error) { + failure = error; + artifact = addDerivedFields( + blockedCoreReport(quest, expected, `Report materialization failed: ${error.message}`), + quest, + ); + } + + await writeJson(resolve(options.output), artifact); + if (failure) { + throw failure; + } +} + +function validateArtifact(report, quest, expected) { + const core = Object.fromEntries(coreKeys.map((key) => [key, report[key]])); + const errors = validateCoreReport(core, quest, expected); + const derived = addDerivedFields(core, quest); + if (stableJson(report.deduplication) !== stableJson(derived.deduplication)) { + errors.push("deduplication fields do not match deterministic values"); + } + if (stableJson(report.childIssuePreview) !== stableJson(derived.childIssuePreview)) { + errors.push("childIssuePreview does not match the deterministic preview"); + } + return errors; +} + +function buildParentIssuePreview(reports, weekKey, priorWeekKey) { + const parentMarker = ``; + const childKeys = reports + .filter(({ result }) => result === "material-change") + .map(({ deduplication }) => deduplication.childKey); + const manifest = { + version: 1, + weekKey, + priorWeekKey, + parentKey: `weekly-refresh:${weekKey}`, + childKeys, + outcomes: Object.fromEntries( + reports.map((report) => [report.questSlug, report.result]), + ), + }; + const table = [ + "| Quest | Result | Learner impact | Child preview |", + "|---|---|---|---|", + ...reports.map((report) => { + const child = + report.result === "material-change" + ? `\`children/${report.questSlug}.md\`` + : "None"; + return `| ${report.questSlug} | ${report.result} | ${report.learnerImpact} | ${child} |`; + }), + ].join("\n"); + const blocked = reports.filter(({ result }) => result === "blocked"); + const unchanged = reports.filter(({ result }) => result === "no-material-change"); + const body = [ + parentMarker, + ``, + "", + "### Weekly summary", + table, + "", + "### Affected quests", + renderList( + reports + .filter(({ result }) => result === "material-change") + .map( + (report) => + `${report.questSlug}: ${report.executiveSummary} (${report.deduplication.childKey})`, + ), + ), + "", + "### No material change", + renderList( + unchanged.map( + (report) => `${report.questSlug}: ${report.executiveSummary}`, + ), + ), + "", + "### Blocked scopes", + renderList( + blocked.map( + (report) => `${report.questSlug}: ${report.blockedReason}`, + ), + ), + "", + "### Run mode", + "Phase 2A staged preview only. No issue, branch, pull request, comment, schedule, or content write is enabled.", + ].join("\n"); + + return { + title: `[Weekly refresh][${weekKey}] Quest content digest`, + labels: ["refresh/plan"], + body, + }; +} + +async function digest(options) { + const quests = await readQuests(); + const expected = { + weekKey: options.week, + priorWeekKey: options["prior-week"], + }; + const context = weekContext(expected.weekKey); + if (expected.priorWeekKey !== context.priorWeekKey) { + throw new Error( + `prior week must be ${context.priorWeekKey} for ${expected.weekKey}`, + ); + } + const reports = []; + const inputErrors = []; + + for (const quest of quests.quests) { + const reportPath = resolve(options["reports-dir"], `${quest.slug}.json`); + try { + const report = JSON.parse(await readFile(reportPath, "utf8")); + const errors = validateArtifact(report, quest, expected); + if (errors.length > 0) { + throw new Error(errors.join("; ")); + } + reports.push(report); + } catch (error) { + inputErrors.push(`${quest.slug}: ${error.message}`); + reports.push( + addDerivedFields( + blockedCoreReport( + quest, + expected, + `Quest artifact unavailable or invalid: ${error.message}`, + ), + quest, + ), + ); + } + } + + const outputDirectory = resolve(options["output-dir"]); + const parentIssuePreview = buildParentIssuePreview( + reports, + expected.weekKey, + expected.priorWeekKey, + ); + const digestValue = { + schemaVersion: 1, + weekKey: expected.weekKey, + priorWeekKey: expected.priorWeekKey, + parentKey: `weekly-refresh:${expected.weekKey}`, + results: reports.map(({ questSlug, result }) => ({ questSlug, result })), + inputErrors, + parentIssuePreview, + reports, + }; + + await writeJson(resolve(outputDirectory, "digest.json"), digestValue); + await mkdir(resolve(outputDirectory, "children"), { recursive: true }); + await writeFile( + resolve(outputDirectory, "parent-issue.md"), + `# ${parentIssuePreview.title}\n\nLabels: ${parentIssuePreview.labels.join(", ")}\n\n${parentIssuePreview.body}\n`, + ); + for (const report of reports) { + if (!report.childIssuePreview) { + continue; + } + await writeFile( + resolve(outputDirectory, "children", `${report.questSlug}.md`), + `# ${report.childIssuePreview.title}\n\nLabels: ${report.childIssuePreview.labels.join(", ")}\n\n${report.childIssuePreview.body}\n`, + ); + } + return digestValue; +} + +function sampleOwnedPath(quest) { + const pattern = + quest.ownedPaths["Azure-Samples/JavaScript-AI-Buildathon"][0]; + return pattern.endsWith("/**") + ? `${pattern.slice(0, -3)}/README.md` + : pattern; +} + +function sampleReport(quest, expected, result) { + const material = result === "material-change"; + const blocked = result === "blocked"; + const path = sampleOwnedPath(quest); + return { + schemaVersion: 1, + weekKey: expected.weekKey, + priorWeekKey: expected.priorWeekKey, + questSlug: quest.slug, + result, + executiveSummary: `${quest.title} ${result} self-test result.`, + currentQuestStatus: blocked ? "Status unavailable." : "Current guidance inspected.", + learnerImpact: material ? "medium" : "none", + officialSourceDeltas: blocked + ? [] + : [ + { + fact: "Self-test fact", + lifecycle: "Not stated by allowed sources", + evidenceUrl: quest.officialSourcePrefixes[0], + evidenceLocation: "Self-test", + observedWording: "Self-test wording", + fingerprint: `content:${quest.slug}`, + }, + ], + contentDrift: material + ? [{ path, section: "Self-test", observation: "Self-test drift" }] + : [], + proposedChanges: material + ? [ + { + repository: "Azure-Samples/JavaScript-AI-Buildathon", + path, + description: "Apply the self-test change.", + }, + ] + : [], + assetImpact: "", + codeTourImpact: [], + validationPlan: material ? ["Run self-test validation."] : [], + risks: blocked ? ["Self-test block."] : [], + humanDecisions: [], + sourceFingerprints: blocked ? [] : [`content:${quest.slug}`], + priorFindingKeys: [], + blockedReason: blocked ? "Self-test blocked reason." : null, + }; +} + +async function selfTest() { + assert.equal(weekContext("2021-W01").priorWeekKey, "2020-W53"); + assert.equal(weekContext("2025-W01").priorWeekKey, "2024-W52"); + assert.throws(() => weekContext("2021-W53"), /does not exist/); + + const quests = await readQuests(); + const expected = { weekKey: "2026-W30", priorWeekKey: "2026-W29" }; + const temporaryDirectory = await mkdtemp( + resolve(tmpdir(), "weekly-refresh-self-test-"), + ); + try { + const materialCore = sampleReport( + quests.quests[0], + expected, + "material-change", + ); + materialCore.sourceFingerprints = [ + "content:z-last", + materialCore.sourceFingerprints[0], + ]; + materialCore.priorFindingKeys = [ + `finding:${"b".repeat(64)}`, + `finding:${"a".repeat(64)}`, + ]; + const agentOutputPath = resolve(temporaryDirectory, "agent-output.json"); + const materializedPath = resolve( + temporaryDirectory, + "materialized", + `${quests.quests[0].slug}.json`, + ); + await writeJson(agentOutputPath, { + items: [ + { + type: "emit_weekly_report", + result: materialCore.result, + report_json: JSON.stringify(materialCore), + }, + ], + }); + await materialize({ + "agent-output": agentOutputPath, + output: materializedPath, + week: expected.weekKey, + "prior-week": expected.priorWeekKey, + quest: quests.quests[0].slug, + }); + const materialized = JSON.parse(await readFile(materializedPath, "utf8")); + assert.equal(materialized.result, "material-change"); + assert.ok(materialized.childIssuePreview); + assert.deepEqual( + validateArtifact(materialized, quests.quests[0], expected), + [], + ); + + const adjacentSource = structuredClone(materialCore); + adjacentSource.officialSourceDeltas[0].evidenceUrl = + `${quests.quests[0].officialSourcePrefixes[1]}-malicious`; + assert.ok( + validateCoreReport(adjacentSource, quests.quests[0], expected).some( + (error) => error.includes("outside the quest source allowlist"), + ), + ); + const traversalPath = structuredClone(materialCore); + traversalPath.contentDrift[0].path = + "01-Local-AI-Development/../.github/workflows/example.yml"; + assert.ok( + validateCoreReport(traversalPath, quests.quests[0], expected).some( + (error) => error.includes("outside the quest ownership boundary"), + ), + ); + const missingOutputPath = resolve( + temporaryDirectory, + "missing-agent-output.json", + ); + const blockedMaterializedPath = resolve( + temporaryDirectory, + "materialized", + "blocked.json", + ); + await writeJson(missingOutputPath, { items: [] }); + await assert.rejects( + materialize({ + "agent-output": missingOutputPath, + output: blockedMaterializedPath, + week: expected.weekKey, + "prior-week": expected.priorWeekKey, + quest: quests.quests[0].slug, + }), + /Expected one emit_weekly_report item/, + ); + assert.equal( + JSON.parse(await readFile(blockedMaterializedPath, "utf8")).result, + "blocked", + ); + + for (const [index, quest] of quests.quests.entries()) { + const result = + index === 0 + ? "material-change" + : index === 1 + ? "blocked" + : "no-material-change"; + const core = sampleReport(quest, expected, result); + assert.deepEqual(validateCoreReport(core, quest, expected), []); + await writeJson( + resolve(temporaryDirectory, "reports", `${quest.slug}.json`), + addDerivedFields(core, quest), + ); + } + + const digestValue = await digest({ + week: expected.weekKey, + "prior-week": expected.priorWeekKey, + "reports-dir": resolve(temporaryDirectory, "reports"), + "output-dir": resolve(temporaryDirectory, "preview"), + }); + assert.equal(digestValue.results.length, 5); + assert.deepEqual(digestValue.inputErrors, []); + assert.equal( + digestValue.reports.filter(({ childIssuePreview }) => childIssuePreview) + .length, + 1, + ); + } finally { + await rm(temporaryDirectory, { recursive: true, force: true }); + } + console.log("Weekly refresh report self-test passed."); +} + +async function main() { + const { command, options } = parseArguments(process.argv.slice(2)); + if (command === "context") { + const context = weekContext(options.week); + if (options["github-output"]) { + await writeFile( + resolve(options["github-output"]), + `week_key=${context.weekKey}\nprior_week_key=${context.priorWeekKey}\nparent_key=${context.parentKey}\n`, + { flag: "a" }, + ); + } + console.log(JSON.stringify(context)); + } else if (command === "materialize") { + for (const key of ["agent-output", "output", "week", "prior-week", "quest"]) { + if (!options[key]) { + throw new Error(`materialize requires --${key}`); + } + } + await materialize(options); + } else if (command === "digest") { + for (const key of [ + "reports-dir", + "output-dir", + "week", + "prior-week", + ]) { + if (!options[key]) { + throw new Error(`digest requires --${key}`); + } + } + await digest(options); + } else if (command === "validate") { + for (const key of ["report", "week", "prior-week", "quest"]) { + if (!options[key]) { + throw new Error(`validate requires --${key}`); + } + } + const quests = await readQuests(); + const quest = quests.quests.find(({ slug }) => slug === options.quest); + if (!quest) { + throw new Error(`Unknown quest slug: ${options.quest}`); + } + const report = JSON.parse(await readFile(resolve(options.report), "utf8")); + const errors = validateArtifact(report, quest, { + weekKey: options.week, + priorWeekKey: options["prior-week"], + }); + if (errors.length > 0) { + throw new Error(errors.join("; ")); + } + console.log(`Validated ${options.report}`); + } else if (command === "self-test") { + await selfTest(); + } else { + throw new Error( + "Usage: weekly-refresh-report.mjs context|materialize|digest|validate|self-test", + ); + } +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + main().catch((error) => { + console.error(error.message); + process.exitCode = 1; + }); +} From 6044021f1f86a5f43897e769a0bf7293eee4b57f Mon Sep 17 00:00:00 2001 From: juliamuiruri4 <40116776+juliamuiruri4@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:21:56 +0300 Subject: [PATCH 2/2] Document report boundary validation Record the URL-segment and repository-path normalization guards enforced before report artifacts are accepted. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/agentic-refresh/report-contract.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/agentic-refresh/report-contract.md b/.github/agentic-refresh/report-contract.md index 68c0e4f..05bebc1 100644 --- a/.github/agentic-refresh/report-contract.md +++ b/.github/agentic-refresh/report-contract.md @@ -18,6 +18,9 @@ Every quest run first emits one version 1 JSON artifact with: A completed report must include source fingerprints. A blocked report must state one concise reason and cannot propose changes. A no-change report cannot propose changes. Invalid or missing quest artifacts become blocked scopes in the digest. +Source and path validation rejects adjacent URL-prefix matches, absolute paths, +backslashes, normalization changes, and traversal segments before materializing +an artifact. A child plan must contain: