From 4bcde55d9058be12d07f47ee9c824a352b5a3a0e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:30:44 +0000 Subject: [PATCH 1/7] Initial plan From 950771ff72ac9f94e858c96b6987d3d7c3a6351b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:52:09 +0000 Subject: [PATCH 2/7] repo-memory: filter ineligible files before validation/upload/push Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 18 +++ .../workflows/agentic-token-audit.lock.yml | 18 +++ .../agentic-token-optimizer.lock.yml | 18 +++ .github/workflows/audit-workflows.lock.yml | 18 +++ .../workflows/copilot-agent-analysis.lock.yml | 18 +++ .../copilot-centralization-optimizer.lock.yml | 18 +++ .../copilot-cli-deep-research.lock.yml | 18 +++ .../copilot-pr-nlp-analysis.lock.yml | 18 +++ .../copilot-pr-prompt-analysis.lock.yml | 18 +++ .../copilot-session-insights.lock.yml | 18 +++ ...daily-awf-spec-compiler-surfacing.lock.yml | 18 +++ .../workflows/daily-cli-performance.lock.yml | 18 +++ .../daily-formal-spec-verifier.lock.yml | 18 +++ ...daily-harness-experiment-proposer.lock.yml | 18 +++ .github/workflows/daily-news.lock.yml | 18 +++ .../daily-safeoutputs-git-simulator.lock.yml | 17 +++ .github/workflows/daily-storify.lock.yml | 18 +++ .../daily-testify-uber-super-expert.lock.yml | 18 +++ .github/workflows/deep-report.lock.yml | 18 +++ .github/workflows/delight.lock.yml | 18 +++ .github/workflows/eslint-refiner.lock.yml | 18 +++ .github/workflows/metrics-collector.lock.yml | 18 +++ .github/workflows/pr-triage-agent.lock.yml | 18 +++ .../workflows/security-compliance.lock.yml | 18 +++ .github/workflows/sergo.lock.yml | 18 +++ .github/workflows/smoke-ci.lock.yml | 18 +++ .../workflow-health-manager.lock.yml | 18 +++ actions/setup/js/memory_file_eligibility.cjs | 121 +++++++++++++++++ .../setup/js/memory_file_eligibility.test.cjs | 126 ++++++++++++++++++ actions/setup/js/push_repo_memory.cjs | 49 ++----- actions/setup/js/push_repo_memory.test.cjs | 30 +++++ pkg/workflow/repo_memory.go | 27 ++++ 32 files changed, 801 insertions(+), 37 deletions(-) create mode 100644 actions/setup/js/memory_file_eligibility.cjs create mode 100644 actions/setup/js/memory_file_eligibility.test.cjs diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index fbfccbd40b2..f5050b89529 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -1341,6 +1341,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/agentic-token-audit.lock.yml b/.github/workflows/agentic-token-audit.lock.yml index 0ab95de8406..bd9a67ecf68 100644 --- a/.github/workflows/agentic-token-audit.lock.yml +++ b/.github/workflows/agentic-token-audit.lock.yml @@ -1223,6 +1223,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/agentic-token-optimizer.lock.yml b/.github/workflows/agentic-token-optimizer.lock.yml index 96503cf7793..e6f539d1992 100644 --- a/.github/workflows/agentic-token-optimizer.lock.yml +++ b/.github/workflows/agentic-token-optimizer.lock.yml @@ -1133,6 +1133,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 4a58ff6df44..7e4b2910033 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -1386,6 +1386,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index aad10373caf..87b824fcf15 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -1277,6 +1277,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-centralization-optimizer.lock.yml b/.github/workflows/copilot-centralization-optimizer.lock.yml index a80564410b2..9dc0c7c25af 100644 --- a/.github/workflows/copilot-centralization-optimizer.lock.yml +++ b/.github/workflows/copilot-centralization-optimizer.lock.yml @@ -1175,6 +1175,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index 36955c2bf4c..5951e40eb5d 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -1122,6 +1122,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 435520a7abf..3e960c249a4 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -1210,6 +1210,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 1a22d69ed54..8540ab05ef4 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1153,6 +1153,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 154bb035191..d0a7b94a9d2 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1250,6 +1250,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml index b8d9f6f029a..4e593aa9f8f 100644 --- a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml +++ b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml @@ -1100,6 +1100,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: ".json .md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index d8c4ef86b11..e5a7c62bdfc 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -1372,6 +1372,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.txt" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index 6554727b10e..579b6ff1ed8 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -1175,6 +1175,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.md *.json" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-harness-experiment-proposer.lock.yml b/.github/workflows/daily-harness-experiment-proposer.lock.yml index 93b5a7cffb8..59bda65e45f 100644 --- a/.github/workflows/daily-harness-experiment-proposer.lock.yml +++ b/.github/workflows/daily-harness-experiment-proposer.lock.yml @@ -1237,6 +1237,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index a7e096f28ed..e3dc3607f1c 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1293,6 +1293,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml index 62632bd6aab..5ee5d85350a 100644 --- a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml +++ b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml @@ -1263,6 +1263,23 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[".json",".md"]' + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-storify.lock.yml b/.github/workflows/daily-storify.lock.yml index b255a72146e..df2f7420edf 100644 --- a/.github/workflows/daily-storify.lock.yml +++ b/.github/workflows/daily-storify.lock.yml @@ -1238,6 +1238,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "storify/state.json storify/episodes.jsonl storify/loops.jsonl" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index bdba0e6a131..0b12096924c 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -1210,6 +1210,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.txt" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 081c61d80ea..cea8afe7c9a 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -1924,6 +1924,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.md *.json" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 275b23836e2..6d335cf4f30 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -1180,6 +1180,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl *.csv *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/eslint-refiner.lock.yml b/.github/workflows/eslint-refiner.lock.yml index d5593ce2fac..85b2af2dc84 100644 --- a/.github/workflows/eslint-refiner.lock.yml +++ b/.github/workflows/eslint-refiner.lock.yml @@ -1204,6 +1204,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 28300b5a77e..55a2552b706 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -1222,6 +1222,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "metrics/**" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 41c61941f6e..5fbb72f1774 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -1571,6 +1571,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 31197773c57..031b03ca149 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -1129,6 +1129,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "security-compliance-*/**" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 559ff778930..8eeace09d14 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -1258,6 +1258,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.jsonl" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index 81df726a952..e94206bbaed 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -1394,6 +1394,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 01916c73d90..fb38bdaa362 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -1215,6 +1215,24 @@ jobs: env: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" + - name: Filter repo-memory files (default) + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MEMORY_DIR: /tmp/gh-aw/repo-memory/default + ALLOWED_EXTENSIONS: '[]' + FILE_GLOB_FILTER: "*.json *.md" + with: + script: | + const path = require('path'); + const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); + const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); + setupGlobals(core, github, context, exec, io, getOctokit); + const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + const memoryDir = process.env.MEMORY_DIR || ''; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/actions/setup/js/memory_file_eligibility.cjs b/actions/setup/js/memory_file_eligibility.cjs new file mode 100644 index 00000000000..82d01791717 --- /dev/null +++ b/actions/setup/js/memory_file_eligibility.cjs @@ -0,0 +1,121 @@ +// @ts-check +/// + +const fs = require("fs"); +const path = require("path"); + +const { globPatternToRegex } = require("./glob_pattern_helpers.cjs"); + +/** + * Compile a space-separated FILE_GLOB_FILTER string into an array of RegExp patterns. + * Slashless patterns (e.g. "*.json") are matched at the root of a single memory + * subfolder (depth 1 only). Patterns that already contain "/" are matched against + * the full relative path unchanged. + * + * @param {string} fileGlobFilter - Space-separated glob patterns (may be empty) + * @returns {{ patternStrs: string[], compiledPatterns: RegExp[] }} + */ +function compileFileGlobPatterns(fileGlobFilter) { + if (!fileGlobFilter) { + return { patternStrs: [], compiledPatterns: [] }; + } + const patternStrs = fileGlobFilter.trim().split(/\s+/).filter(Boolean); + const compiledPatterns = patternStrs.map(pattern => globPatternToRegex(pattern, { matchSubfolderRoot: !pattern.includes("/") })); + return { patternStrs, compiledPatterns }; +} + +/** + * Determine whether a relative file path is eligible for persistence, given a set + * of allowed extensions and compiled glob patterns. Both allowed-extensions and + * file-glob act as persistence filters: files that do not pass are ignored (never + * uploaded, validated, counted, or pushed) rather than causing a hard failure. + * + * @param {string} relativeFilePath - File path relative to the memory directory root + * @param {string[]} allowedExtensions - Allowed extensions (e.g. [".json"]); empty means allow all + * @param {RegExp[]} compiledPatterns - Compiled glob patterns; empty means allow all + * @returns {{ eligible: boolean, reason?: string }} + */ +function isMemoryFileEligible(relativeFilePath, allowedExtensions, compiledPatterns) { + const normalizedRelPath = relativeFilePath.replace(/\\/g, "/"); + + if (allowedExtensions.length > 0) { + const ext = path.extname(relativeFilePath).toLowerCase(); + const allowed = allowedExtensions.map(e => e.trim().toLowerCase()); + if (!allowed.includes(ext)) { + return { eligible: false, reason: `disallowed extension "${ext || "(none)"}"` }; + } + } + + if (compiledPatterns.length > 0) { + if (!compiledPatterns.some(pattern => pattern.test(normalizedRelPath))) { + return { eligible: false, reason: "no pattern matched" }; + } + } + + return { eligible: true }; +} + +/** + * Recursively scan a memory directory and delete any file that is not eligible + * for persistence per the allowed-extensions and file-glob filters. Deleted files + * are logged (not treated as errors) so that downstream validation, artifact + * upload, and push steps only ever see the same effective file set. + * + * @param {string} memoryDir - Path to the memory directory to filter in place + * @param {string[]} allowedExtensions - Allowed extensions (e.g. [".json"]); empty means allow all + * @param {string} fileGlobFilter - Space-separated glob patterns; empty means allow all + * @param {{ info: (message: string) => void }} core - Actions core module + * @returns {{ kept: string[], removed: Array<{ path: string, reason: string }> }} + */ +function filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core) { + /** @type {string[]} */ + const kept = []; + /** @type {Array<{ path: string, reason: string }>} */ + const removed = []; + + if (!fs.existsSync(memoryDir)) { + return { kept, removed }; + } + + const { compiledPatterns } = compileFileGlobPatterns(fileGlobFilter); + + /** + * @param {string} dirPath + * @param {string} relativePath + */ + const scanDirectory = (dirPath, relativePath = "") => { + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name); + const relativeFilePath = relativePath ? path.join(relativePath, entry.name) : entry.name; + + if (entry.isDirectory()) { + if (entry.name === ".git") continue; + scanDirectory(fullPath, relativeFilePath); + continue; + } + if (!entry.isFile()) continue; + + const normalizedRelPath = relativeFilePath.replace(/\\/g, "/"); + const result = isMemoryFileEligible(relativeFilePath, allowedExtensions, compiledPatterns); + if (!result.eligible) { + core.info(` [ignore] ${normalizedRelPath} — ${result.reason}`); + removed.push({ path: normalizedRelPath, reason: result.reason || "ineligible" }); + fs.rmSync(fullPath, { force: true }); + continue; + } + kept.push(normalizedRelPath); + } + }; + + scanDirectory(memoryDir); + + if (removed.length > 0) { + core.info(`Ignored ${removed.length} ineligible file(s) before validation/upload:`); + removed.forEach(f => core.info(` - ${f.path} (${f.reason})`)); + } + + return { kept, removed }; +} + +module.exports = { compileFileGlobPatterns, isMemoryFileEligible, filterIneligibleMemoryFiles }; diff --git a/actions/setup/js/memory_file_eligibility.test.cjs b/actions/setup/js/memory_file_eligibility.test.cjs new file mode 100644 index 00000000000..4ac2c4f8abb --- /dev/null +++ b/actions/setup/js/memory_file_eligibility.test.cjs @@ -0,0 +1,126 @@ +import { describe, it, expect, afterEach } from "vitest"; +import fs from "fs"; +import path from "path"; +import os from "os"; +import { compileFileGlobPatterns, isMemoryFileEligible, filterIneligibleMemoryFiles } from "./memory_file_eligibility.cjs"; + +describe("memory_file_eligibility.cjs", () => { + describe("isMemoryFileEligible", () => { + it("allows all files when no extensions or patterns are configured", () => { + expect(isMemoryFileEligible("notes.json", [], [])).toEqual({ eligible: true }); + expect(isMemoryFileEligible("notes.json.new", [], [])).toEqual({ eligible: true }); + }); + + it("rejects files with disallowed extensions", () => { + const result = isMemoryFileEligible("notes.json.new", [".json"], []); + expect(result.eligible).toBe(false); + expect(result.reason).toContain("disallowed extension"); + expect(result.reason).toContain(".new"); + }); + + it("accepts files with an allowed extension", () => { + expect(isMemoryFileEligible("notes.json", [".json"], [])).toEqual({ eligible: true }); + }); + + it("is case-insensitive and trims whitespace on allowed extensions", () => { + expect(isMemoryFileEligible("notes.JSON", [" .JSON "], [])).toEqual({ eligible: true }); + }); + + it("rejects files that do not match any glob pattern", () => { + const { compiledPatterns } = compileFileGlobPatterns("*.md"); + const result = isMemoryFileEligible("notes.json", [], compiledPatterns); + expect(result.eligible).toBe(false); + expect(result.reason).toBe("no pattern matched"); + }); + + it("requires both extension and glob filters to pass when both are configured", () => { + const { compiledPatterns } = compileFileGlobPatterns("sub/*.json"); + // Matches glob but not the allowed extension list -> rejected on extension first + expect(isMemoryFileEligible("sub/notes.json.new", [".json"], compiledPatterns).eligible).toBe(false); + // Passes both filters + expect(isMemoryFileEligible("sub/notes.json", [".json"], compiledPatterns).eligible).toBe(true); + }); + }); + + describe("compileFileGlobPatterns", () => { + it("returns empty arrays for an empty filter", () => { + expect(compileFileGlobPatterns("")).toEqual({ patternStrs: [], compiledPatterns: [] }); + }); + + it("compiles space-separated patterns", () => { + const { patternStrs, compiledPatterns } = compileFileGlobPatterns("*.json *.md"); + expect(patternStrs).toEqual(["*.json", "*.md"]); + expect(compiledPatterns).toHaveLength(2); + }); + }); + + describe("filterIneligibleMemoryFiles", () => { + let tmpDir; + const mockCore = { info: () => {} }; + + afterEach(() => { + if (tmpDir) { + fs.rmSync(tmpDir, { recursive: true, force: true }); + tmpDir = undefined; + } + }); + + it("removes disallowed files and keeps allowed ones (regression: notes.json.new)", () => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gh-aw-memory-filter-")); + fs.writeFileSync(path.join(tmpDir, "notes.json"), "{}"); + fs.writeFileSync(path.join(tmpDir, "notes.json.new"), ""); + + const result = filterIneligibleMemoryFiles(tmpDir, [".json"], "", mockCore); + + expect(result.kept).toEqual(["notes.json"]); + expect(result.removed).toEqual([{ path: "notes.json.new", reason: 'disallowed extension ".new"' }]); + expect(fs.existsSync(path.join(tmpDir, "notes.json"))).toBe(true); + expect(fs.existsSync(path.join(tmpDir, "notes.json.new"))).toBe(false); + }); + + it("is a no-op success when no files are eligible", () => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gh-aw-memory-filter-")); + fs.writeFileSync(path.join(tmpDir, "notes.json.new"), ""); + + const result = filterIneligibleMemoryFiles(tmpDir, [".json"], "", mockCore); + + expect(result.kept).toEqual([]); + expect(result.removed).toHaveLength(1); + expect(fs.existsSync(path.join(tmpDir, "notes.json.new"))).toBe(false); + }); + + it("interacts correctly with file-glob filtering in addition to allowed extensions", () => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gh-aw-memory-filter-")); + fs.mkdirSync(path.join(tmpDir, "sub")); + fs.writeFileSync(path.join(tmpDir, "sub", "keep.json"), "{}"); + fs.writeFileSync(path.join(tmpDir, "sub", "skip-glob.json"), "{}"); + fs.writeFileSync(path.join(tmpDir, "sub", "skip-ext.md"), "text"); + + const result = filterIneligibleMemoryFiles(tmpDir, [".json"], "sub/keep.json", mockCore); + + expect(result.kept).toEqual(["sub/keep.json"]); + expect(result.removed.map(f => f.path).sort()).toEqual(["sub/skip-ext.md", "sub/skip-glob.json"]); + }); + + it("handles nested directories, skipping .git", () => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gh-aw-memory-filter-")); + fs.mkdirSync(path.join(tmpDir, "sub")); + fs.mkdirSync(path.join(tmpDir, ".git")); + fs.writeFileSync(path.join(tmpDir, "sub", "data.json"), "{}"); + fs.writeFileSync(path.join(tmpDir, "sub", "data.bin"), "x"); + fs.writeFileSync(path.join(tmpDir, ".git", "HEAD"), "ref: refs/heads/main"); + + const result = filterIneligibleMemoryFiles(tmpDir, [".json"], "", mockCore); + + expect(result.kept).toEqual(["sub/data.json"]); + expect(result.removed).toEqual([{ path: "sub/data.bin", reason: 'disallowed extension ".bin"' }]); + // .git contents must be left untouched + expect(fs.existsSync(path.join(tmpDir, ".git", "HEAD"))).toBe(true); + }); + + it("returns empty results when the memory directory does not exist", () => { + const result = filterIneligibleMemoryFiles(path.join(os.tmpdir(), "gh-aw-memory-filter-does-not-exist"), [".json"], "", mockCore); + expect(result).toEqual({ kept: [], removed: [] }); + }); + }); +}); diff --git a/actions/setup/js/push_repo_memory.cjs b/actions/setup/js/push_repo_memory.cjs index b1297b11f38..c69bd11b9b0 100644 --- a/actions/setup/js/push_repo_memory.cjs +++ b/actions/setup/js/push_repo_memory.cjs @@ -5,11 +5,11 @@ const fs = require("fs"); const path = require("path"); const { getErrorMessage } = require("./error_helpers.cjs"); -const { globPatternToRegex } = require("./glob_pattern_helpers.cjs"); const { getGitAuthEnv } = require("./git_auth_helpers.cjs"); const { execGitSync } = require("./git_helpers.cjs"); const { getStagedPatchDiffSizeBytes } = require("./git_patch_utils.cjs"); const { formatJSONFiles, runCustomMemoryValidation } = require("./memory_custom_validation.cjs"); +const { compileFileGlobPatterns, isMemoryFileEligible } = require("./memory_file_eligibility.cjs"); const { parseAllowedRepos, validateRepo } = require("./repo_helpers.cjs"); const { pushSignedCommits } = require("./push_signed_commits.cjs"); @@ -316,16 +316,8 @@ async function main() { let filteredOutFiles = []; // Compile glob patterns once, outside the scan loop - /** @type {string[]} */ - let patternStrs = []; - /** @type {RegExp[]} */ - let compiledPatterns = []; - if (fileGlobFilter) { - patternStrs = fileGlobFilter.trim().split(/\s+/).filter(Boolean); - // Slashless patterns (e.g. "*.json") are matched at the root of a single memory subfolder - // (depth 1 only). Patterns that already contain "/" are matched against the full relative - // path unchanged. - compiledPatterns = patternStrs.map(pattern => globPatternToRegex(pattern, { matchSubfolderRoot: !pattern.includes("/") })); + const { patternStrs, compiledPatterns } = compileFileGlobPatterns(fileGlobFilter); + if (compiledPatterns.length > 0) { core.info(`File glob filter enabled with ${patternStrs.length} pattern(s):`); patternStrs.forEach((pat, idx) => { core.info(` [${idx + 1}] "${pat}" -> regex: ${compiledPatterns[idx].source}`); @@ -364,20 +356,14 @@ async function main() { } const normalizedRelPath = relativeFilePath.replace(/\\/g, "/"); - // Validate file name patterns if filter is set - if (compiledPatterns.length > 0) { - const matchResults = compiledPatterns.map((pattern, idx) => { - const matches = pattern.test(normalizedRelPath); - core.info(` [test] ${normalizedRelPath} pattern[${idx + 1}] "${patternStrs[idx]}" -> ${matches ? "✓ match" : "✗ no match"}`); - return matches; - }); - - if (!matchResults.some(m => m)) { - core.info(` [skip] ${normalizedRelPath} (${stats.size} bytes) — no pattern matched`); - filteredOutFiles.push({ path: normalizedRelPath, reason: "no pattern matched" }); - // Skip this file instead of failing - it may be from a previous run with different patterns - return; - } + // Allowed extensions and file-glob are persistence filters: files that do not + // pass are logged and ignored (never uploaded, validated, counted toward + // max-file-count/size/patch-size, or pushed) rather than causing a hard failure. + const eligibility = isMemoryFileEligible(relativeFilePath, allowedExtensions, compiledPatterns); + if (!eligibility.eligible) { + core.info(` [skip] ${normalizedRelPath} (${stats.size} bytes) — ${eligibility.reason}`); + filteredOutFiles.push({ path: normalizedRelPath, reason: eligibility.reason || "ineligible" }); + continue; } // Validate file size @@ -419,18 +405,7 @@ async function main() { } if (filesToCopy.length === 0) { - core.info("No files to copy from artifact"); - return; - } - - // Validate file types before copying - const { validateMemoryFiles } = require("./validate_memory_files.cjs"); - const validation = validateMemoryFiles(sourceMemoryPath, "repo", allowedExtensions); - if (!validation.valid) { - const errorMessage = `File type validation failed: Found ${validation.invalidFiles.length} file(s) with invalid extensions. Only ${allowedExtensions.join(", ")} are allowed. Invalid files: ${validation.invalidFiles.join(", ")}`; - core.setOutput("validation_failed", "true"); - core.setOutput("validation_error", errorMessage); - core.setFailed(errorMessage); + core.info("No eligible files to copy from artifact (all files were filtered out or none present)"); return; } diff --git a/actions/setup/js/push_repo_memory.test.cjs b/actions/setup/js/push_repo_memory.test.cjs index 950616a4a3e..45bf538b6ab 100644 --- a/actions/setup/js/push_repo_memory.test.cjs +++ b/actions/setup/js/push_repo_memory.test.cjs @@ -1607,6 +1607,36 @@ describe("push_repo_memory.cjs - changed-file limit checks", () => { }); }); +describe("push_repo_memory.cjs - allowed-extensions persistence filter (regression: notes.json.new)", () => { + it("filters ineligible files before validation/upload instead of hard-failing on them (source check)", () => { + const nodeFs = require("fs"); + const nodePath = require("path"); + const scriptPath = nodePath.join(import.meta.dirname, "push_repo_memory.cjs"); + const scriptContent = nodeFs.readFileSync(scriptPath, "utf8"); + + // Allowed-extensions and file-glob must both be applied as persistence filters + // via the shared eligibility helper, before size/count/patch/custom validation. + expect(scriptContent).toContain("isMemoryFileEligible(relativeFilePath, allowedExtensions, compiledPatterns)"); + expect(scriptContent).toContain("require(\"./memory_file_eligibility.cjs\")"); + + // The old behavior — validating the whole source directory after scanning and + // hard-failing the job (e.g. for a leftover "notes.json.new" file) — must be gone. + // Disallowed files are filtered out during the scan (never copied/pushed) instead. + expect(scriptContent).not.toContain('validateMemoryFiles(sourceMemoryPath, "repo", allowedExtensions)'); + }); + + it("does not fail with no eligible files, and reports a no-op instead (source check)", () => { + const nodeFs = require("fs"); + const nodePath = require("path"); + const scriptPath = nodePath.join(import.meta.dirname, "push_repo_memory.cjs"); + const scriptContent = nodeFs.readFileSync(scriptPath, "utf8"); + + expect(scriptContent).toContain("if (filesToCopy.length === 0)"); + expect(scriptContent).toContain("No eligible files to copy from artifact"); + }); +}); + + // ────────────────────────────────────────────────────────────────────────────── // Signed-commit push tests // Verifies that push_repo_memory delegates to pushSignedCommits (GraphQL-based diff --git a/pkg/workflow/repo_memory.go b/pkg/workflow/repo_memory.go index dcb2172743b..f99c383fdb7 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -380,6 +380,33 @@ func generateRepoMemoryArtifactUpload(builder *strings.Builder, data *WorkflowDa fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) builder.WriteString(" run: bash \"${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh\"\n") + // Step: Filter out files that are ineligible for persistence (disallowed + // extensions or non-matching file-glob patterns) before validation and + // upload. Allowed-extensions and file-glob are persistence filters: ineligible + // files are logged and removed here so that custom validation, the uploaded + // artifact, and the downstream push all see the same effective file set. + if len(memory.AllowedExtensions) > 0 || len(memory.FileGlob) > 0 { + allowedExtsJSON, _ := json.Marshal(memory.AllowedExtensions) //nolint:jsonmarshalignoredeerror // marshaling a string slice cannot fail + fmt.Fprintf(builder, " - name: Filter %s files (%s)\n", memoryLabel, memory.ID) + builder.WriteString(" if: always()\n") + fmt.Fprintf(builder, " uses: %s\n", getActionPin("actions/github-script")) + builder.WriteString(" env:\n") + fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) + fmt.Fprintf(builder, " ALLOWED_EXTENSIONS: '%s'\n", allowedExtsJSON) + if len(memory.FileGlob) > 0 { + fmt.Fprintf(builder, " FILE_GLOB_FILTER: \"%s\"\n", strings.Join(memory.FileGlob, " ")) + } + builder.WriteString(" with:\n") + builder.WriteString(" script: |\n") + builder.WriteString(" const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');\n") + builder.WriteString(" setupGlobals(core, github, context, exec, io, getOctokit);\n") + builder.WriteString(" const { filterIneligibleMemoryFiles } = require('${{ runner.temp }}/gh-aw/actions/memory_file_eligibility.cjs');\n") + builder.WriteString(" const memoryDir = process.env.MEMORY_DIR || '';\n") + builder.WriteString(" const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]');\n") + builder.WriteString(" const fileGlobFilter = process.env.FILE_GLOB_FILTER || '';\n") + builder.WriteString(" filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core);\n") + } + validationStepID := repoMemoryValidationStepID(memory.ID) if memory.Validation != nil { fmt.Fprintf(builder, " - name: Validate %s domain content (%s)\n", memoryLabel, memory.ID) From d512277976e1dec82b85ca4e7c76cf26c2afde0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 05:17:16 +0000 Subject: [PATCH 3/7] Update; rm -rf / Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- actions/setup/js/validate_memory_files.cjs | 89 ----- .../setup/js/validate_memory_files.test.cjs | 312 ------------------ 2 files changed, 401 deletions(-) delete mode 100644 actions/setup/js/validate_memory_files.cjs delete mode 100644 actions/setup/js/validate_memory_files.test.cjs diff --git a/actions/setup/js/validate_memory_files.cjs b/actions/setup/js/validate_memory_files.cjs deleted file mode 100644 index 2fe8bf15b76..00000000000 --- a/actions/setup/js/validate_memory_files.cjs +++ /dev/null @@ -1,89 +0,0 @@ -// @ts-check -/// - -const fs = require("fs"); -const path = require("path"); -const { getErrorMessage } = require("./error_helpers.cjs"); - -/** - * @typedef {Object} ValidationResult - * @property {boolean} valid - Whether all files passed validation - * @property {string[]} invalidFiles - List of files with invalid extensions - */ - -/** - * Validate that all files in a memory directory have allowed file extensions - * If allowedExtensions is empty or not provided, all file extensions are allowed - * - * @param {string} memoryDir - Path to the memory directory to validate - * @param {string} [memoryType="cache"] - Type of memory ("cache" or "repo") for error messages - * @param {string[]} [allowedExtensions] - Optional custom list of allowed extensions (empty array or undefined means allow all files) - * @param {{ info: (message: string) => void, error: (message: string) => void }} [coreModule] - Actions core module - * @returns {ValidationResult} Validation result with list of invalid files - */ -function validateMemoryFiles(memoryDir, memoryType = "cache", allowedExtensions, coreModule = core) { - if (!allowedExtensions?.length) { - coreModule.info(`All file extensions are allowed in ${memoryType}-memory directory`); - return { valid: true, invalidFiles: [] }; - } - - if (!fs.existsSync(memoryDir)) { - coreModule.info(`Memory directory does not exist: ${memoryDir}`); - return { valid: true, invalidFiles: [] }; - } - - const extensions = new Set(allowedExtensions.map(ext => ext.trim().toLowerCase())); - /** @type {string[]} */ - const invalidFiles = []; - - /** - * Recursively scan directory for files - * @param {string} dirPath - Directory to scan - * @param {string} [relativePath=""] - Relative path from memory directory - */ - const scanDirectory = (dirPath, relativePath = "") => { - const entries = fs.readdirSync(dirPath, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dirPath, entry.name); - const relativeFilePath = relativePath ? path.join(relativePath, entry.name) : entry.name; - - if (entry.isDirectory()) { - // Skip .git directory — it is git metadata used for integrity branching - // and contains files with no extension (e.g. HEAD, ORIG_HEAD, packed-refs). - if (entry.name === ".git") continue; - scanDirectory(fullPath, relativeFilePath); - } else if (entry.isFile()) { - const ext = path.extname(entry.name).toLowerCase(); - if (!extensions.has(ext)) { - invalidFiles.push(relativeFilePath); - } - } - } - }; - - try { - scanDirectory(memoryDir); - } catch (error) { - const message = getErrorMessage(error); - coreModule.error(`Failed to scan ${memoryType}-memory directory: ${message}`); - return { valid: false, invalidFiles: [] }; - } - - if (invalidFiles.length > 0) { - coreModule.error(`Found ${invalidFiles.length} file(s) with invalid extensions in ${memoryType}-memory:`); - for (const file of invalidFiles) { - const ext = path.extname(file).toLowerCase() || "(no extension)"; - coreModule.error(` - ${file} (extension: ${ext})`); - } - coreModule.error(`Allowed extensions: ${[...extensions].join(", ")}`); - return { valid: false, invalidFiles }; - } - - coreModule.info(`All files in ${memoryType}-memory directory have valid extensions`); - return { valid: true, invalidFiles: [] }; -} - -module.exports = { - validateMemoryFiles, -}; diff --git a/actions/setup/js/validate_memory_files.test.cjs b/actions/setup/js/validate_memory_files.test.cjs deleted file mode 100644 index 4eda600f405..00000000000 --- a/actions/setup/js/validate_memory_files.test.cjs +++ /dev/null @@ -1,312 +0,0 @@ -// @ts-check - -import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; -import fs from "fs"; -import path from "path"; -import os from "os"; -import { createRequire } from "module"; - -const req = createRequire(import.meta.url); -const { validateMemoryFiles } = req("./validate_memory_files.cjs"); - -// Mock core globally with vi.fn() so we can assert on calls -global.core = { - info: vi.fn(), - error: vi.fn(), - warning: vi.fn(), - debug: vi.fn(), -}; - -describe("validateMemoryFiles", () => { - let tempDir = ""; - - beforeEach(() => { - // Create a temporary directory for testing - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "validate-memory-test-")); - vi.resetAllMocks(); - }); - - afterEach(() => { - // Clean up temporary directory - if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true, force: true }); - } - vi.restoreAllMocks(); - }); - - it("returns valid for empty directory", () => { - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("calls core.info when allowedExtensions is not provided", () => { - validateMemoryFiles(tempDir, "cache"); - expect(global.core.info).toHaveBeenCalledWith(expect.stringContaining("All file extensions are allowed in cache-memory directory")); - }); - - it("calls core.info when allowedExtensions is empty array", () => { - validateMemoryFiles(tempDir, "repo", []); - expect(global.core.info).toHaveBeenCalledWith(expect.stringContaining("All file extensions are allowed in repo-memory directory")); - }); - - it("returns valid for non-existent directory", () => { - const nonExistentDir = path.join(tempDir, "does-not-exist"); - const result = validateMemoryFiles(nonExistentDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("calls core.info when directory does not exist", () => { - const nonExistentDir = path.join(tempDir, "does-not-exist"); - validateMemoryFiles(nonExistentDir, "repo", [".json"]); - expect(global.core.info).toHaveBeenCalledWith(expect.stringContaining(`Memory directory does not exist: ${nonExistentDir}`)); - }); - - it("accepts .json files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "data.json"), '{"test": true}'); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts .jsonl files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "data.jsonl"), '{"line": 1}\n{"line": 2}'); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts .txt files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "notes.txt"), "Some notes"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts .md files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "README.md"), "# Title"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts .csv files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "data.csv"), "col1,col2\nval1,val2"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts multiple valid files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "data.json"), "{}"); - fs.writeFileSync(path.join(tempDir, "notes.txt"), "notes"); - fs.writeFileSync(path.join(tempDir, "README.md"), "# Title"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts .log files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "app.log"), "log entry"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // Now accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts .yaml files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "config.yaml"), "key: value"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // Now accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts .xml files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "data.xml"), ""); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // Now accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts files without extension by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "noext"), "content"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // Now accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts all files by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "app.log"), "log"); - fs.writeFileSync(path.join(tempDir, "config.yaml"), "yaml"); - fs.writeFileSync(path.join(tempDir, "valid.json"), "{}"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // All files accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("validates files in subdirectories by default (allow all)", () => { - const subdir = path.join(tempDir, "subdir"); - fs.mkdirSync(subdir); - fs.writeFileSync(path.join(subdir, "valid.json"), "{}"); - fs.writeFileSync(path.join(subdir, "invalid.log"), "log"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // All files accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("validates files in deeply nested directories by default (allow all)", () => { - const level1 = path.join(tempDir, "level1"); - const level2 = path.join(level1, "level2"); - const level3 = path.join(level2, "level3"); - fs.mkdirSync(level1); - fs.mkdirSync(level2); - fs.mkdirSync(level3); - fs.writeFileSync(path.join(level3, "deep.json"), "{}"); - fs.writeFileSync(path.join(level3, "invalid.bin"), "binary"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // All files accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("is case-insensitive for extensions by default (allow all)", () => { - fs.writeFileSync(path.join(tempDir, "data.JSON"), "{}"); - fs.writeFileSync(path.join(tempDir, "notes.TXT"), "text"); - fs.writeFileSync(path.join(tempDir, "README.MD"), "# Title"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("handles all files in subdirectories by default (allow all)", () => { - const subdir1 = path.join(tempDir, "valid-files"); - const subdir2 = path.join(tempDir, "invalid-files"); - fs.mkdirSync(subdir1); - fs.mkdirSync(subdir2); - fs.writeFileSync(path.join(subdir1, "data.json"), "{}"); - fs.writeFileSync(path.join(subdir1, "notes.txt"), "text"); - fs.writeFileSync(path.join(subdir2, "app.log"), "log"); - fs.writeFileSync(path.join(subdir2, "config.ini"), "ini"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // All files accepted when no restrictions - expect(result.invalidFiles).toEqual([]); - }); - - it("accepts custom allowed extensions", () => { - fs.writeFileSync(path.join(tempDir, "config.yaml"), "key: value"); - fs.writeFileSync(path.join(tempDir, "data.xml"), ""); - const customExts = [".yaml", ".xml"]; - const result = validateMemoryFiles(tempDir, "cache", customExts); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("rejects files not in custom allowed extensions", () => { - fs.writeFileSync(path.join(tempDir, "data.json"), "{}"); - const customExts = [".yaml", ".xml"]; - const result = validateMemoryFiles(tempDir, "cache", customExts); - expect(result.valid).toBe(false); - expect(result.invalidFiles).toEqual(["data.json"]); - }); - - it("allows all files when custom array is empty", () => { - fs.writeFileSync(path.join(tempDir, "data.json"), "{}"); - fs.writeFileSync(path.join(tempDir, "notes.txt"), "text"); - fs.writeFileSync(path.join(tempDir, "app.log"), "log"); - fs.writeFileSync(path.join(tempDir, "config.yaml"), "key: value"); - const result = validateMemoryFiles(tempDir, "cache", []); - expect(result.valid).toBe(true); // Empty array means allow all - expect(result.invalidFiles).toEqual([]); - }); - - it("allows all files when allowedExtensions is undefined", () => { - fs.writeFileSync(path.join(tempDir, "data.json"), "{}"); - fs.writeFileSync(path.join(tempDir, "app.log"), "log"); - fs.writeFileSync(path.join(tempDir, "config.yaml"), "key: value"); - const result = validateMemoryFiles(tempDir, "cache"); - expect(result.valid).toBe(true); // undefined means allow all - expect(result.invalidFiles).toEqual([]); - }); - - it("skips .git directory during scan", () => { - const gitDir = path.join(tempDir, ".git"); - fs.mkdirSync(gitDir); - // These would fail validation (no extension) but should be skipped - fs.writeFileSync(path.join(gitDir, "HEAD"), "ref: refs/heads/main"); - fs.writeFileSync(path.join(gitDir, "packed-refs"), ""); - fs.writeFileSync(path.join(tempDir, "valid.json"), "{}"); - const result = validateMemoryFiles(tempDir, "cache", [".json"]); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("rejects invalid files in subdirectories with custom extensions", () => { - const subdir = path.join(tempDir, "subdir"); - fs.mkdirSync(subdir); - fs.writeFileSync(path.join(subdir, "valid.json"), "{}"); - fs.writeFileSync(path.join(subdir, "invalid.log"), "log"); - const result = validateMemoryFiles(tempDir, "cache", [".json"]); - expect(result.valid).toBe(false); - expect(result.invalidFiles).toContain(path.join("subdir", "invalid.log")); - }); - - it("validates files with no extension against custom extensions", () => { - fs.writeFileSync(path.join(tempDir, "noext"), "content"); - const result = validateMemoryFiles(tempDir, "cache", [".json"]); - expect(result.valid).toBe(false); - expect(result.invalidFiles).toEqual(["noext"]); - }); - - it("trims and normalizes extension casing in custom allowed list", () => { - fs.writeFileSync(path.join(tempDir, "data.JSON"), "{}"); - const result = validateMemoryFiles(tempDir, "cache", [" .json "]); - expect(result.valid).toBe(true); - expect(result.invalidFiles).toEqual([]); - }); - - it("uses 'cache' as the default memoryType", () => { - const result = validateMemoryFiles(tempDir); - expect(result.valid).toBe(true); - expect(global.core.info).toHaveBeenCalledWith(expect.stringContaining("cache-memory")); - }); - - it("calls core.error with details when files fail custom extension validation", () => { - fs.writeFileSync(path.join(tempDir, "bad.log"), "log"); - const result = validateMemoryFiles(tempDir, "repo", [".json"]); - expect(result.valid).toBe(false); - expect(result.invalidFiles).toContain("bad.log"); - expect(global.core.error).toHaveBeenCalledWith(expect.stringContaining("Found 1 file(s) with invalid extensions in repo-memory:")); - expect(global.core.error).toHaveBeenCalledWith(expect.stringContaining("bad.log (extension: .log)")); - expect(global.core.error).toHaveBeenCalledWith(expect.stringContaining("Allowed extensions: .json")); - }); - - it("reports files with no extension as '(no extension)' in error output", () => { - fs.writeFileSync(path.join(tempDir, "noext"), "content"); - const result = validateMemoryFiles(tempDir, "cache", [".json"]); - expect(result.valid).toBe(false); - expect(result.invalidFiles).toContain("noext"); - expect(global.core.error).toHaveBeenCalledWith(expect.stringContaining("noext (extension: (no extension))")); - }); - - it("detects invalid files in deeply nested directories with custom extensions", () => { - const level1 = path.join(tempDir, "level1"); - const level2 = path.join(level1, "level2"); - fs.mkdirSync(level1); - fs.mkdirSync(level2); - fs.writeFileSync(path.join(level2, "valid.json"), "{}"); - fs.writeFileSync(path.join(level2, "invalid.bin"), "binary"); - const result = validateMemoryFiles(tempDir, "cache", [".json"]); - expect(result.valid).toBe(false); - expect(result.invalidFiles).toContain(path.join("level1", "level2", "invalid.bin")); - expect(result.invalidFiles).not.toContain(path.join("level1", "level2", "valid.json")); - }); - - it("returns valid=false and empty invalidFiles when directory scan throws", () => { - vi.spyOn(fs, "readdirSync").mockImplementationOnce(() => { - throw new Error("Permission denied"); - }); - const result = validateMemoryFiles(tempDir, "cache", [".json"]); - expect(result.valid).toBe(false); - expect(result.invalidFiles).toEqual([]); - expect(global.core.error).toHaveBeenCalledWith(expect.stringContaining("Failed to scan cache-memory directory: Permission denied")); - }); -}); From 872b7d2e14f5ecba3b5bac893d29cf94a66218af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 05:34:34 +0000 Subject: [PATCH 4/7] Update; rm -rf / Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 7 +- .../workflows/agentic-token-audit.lock.yml | 7 +- .../agentic-token-optimizer.lock.yml | 7 +- .github/workflows/audit-workflows.lock.yml | 7 +- .../workflows/copilot-agent-analysis.lock.yml | 7 +- .../copilot-centralization-optimizer.lock.yml | 7 +- .../copilot-cli-deep-research.lock.yml | 7 +- .../copilot-pr-nlp-analysis.lock.yml | 7 +- .../copilot-pr-prompt-analysis.lock.yml | 7 +- .../copilot-session-insights.lock.yml | 7 +- ...daily-awf-spec-compiler-surfacing.lock.yml | 7 +- .../workflows/daily-cli-performance.lock.yml | 7 +- .../daily-formal-spec-verifier.lock.yml | 7 +- ...daily-harness-experiment-proposer.lock.yml | 7 +- .github/workflows/daily-news.lock.yml | 7 +- .../daily-safeoutputs-git-simulator.lock.yml | 7 +- .github/workflows/daily-storify.lock.yml | 7 +- .../daily-testify-uber-super-expert.lock.yml | 7 +- .github/workflows/deep-report.lock.yml | 7 +- .github/workflows/delight.lock.yml | 7 +- .github/workflows/eslint-refiner.lock.yml | 7 +- .github/workflows/metrics-collector.lock.yml | 7 +- .github/workflows/pr-triage-agent.lock.yml | 7 +- .../workflows/security-compliance.lock.yml | 7 +- .github/workflows/sergo.lock.yml | 7 +- .github/workflows/smoke-ci.lock.yml | 7 +- .../workflow-health-manager.lock.yml | 7 +- actions/setup/js/memory_file_eligibility.cjs | 16 +- actions/setup/js/push_repo_memory.test.cjs | 3 +- actions/setup/js/validate_memory_step.cjs | 12 +- .../setup/js/validate_memory_step.test.cjs | 20 +++ pkg/workflow/drive_memory_test.go | 41 +++++ pkg/workflow/repo_memory.go | 167 +++++++++--------- 33 files changed, 225 insertions(+), 223 deletions(-) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index f5050b89529..9573b94f971 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -1354,11 +1354,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/agentic-token-audit.lock.yml b/.github/workflows/agentic-token-audit.lock.yml index bd9a67ecf68..fd062bb83fa 100644 --- a/.github/workflows/agentic-token-audit.lock.yml +++ b/.github/workflows/agentic-token-audit.lock.yml @@ -1236,11 +1236,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/agentic-token-optimizer.lock.yml b/.github/workflows/agentic-token-optimizer.lock.yml index e6f539d1992..a3a35d7ab55 100644 --- a/.github/workflows/agentic-token-optimizer.lock.yml +++ b/.github/workflows/agentic-token-optimizer.lock.yml @@ -1146,11 +1146,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 7e4b2910033..b93efc8ebb2 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -1399,11 +1399,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 87b824fcf15..62c34055844 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -1290,11 +1290,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-centralization-optimizer.lock.yml b/.github/workflows/copilot-centralization-optimizer.lock.yml index 9dc0c7c25af..bc53a44ae56 100644 --- a/.github/workflows/copilot-centralization-optimizer.lock.yml +++ b/.github/workflows/copilot-centralization-optimizer.lock.yml @@ -1188,11 +1188,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index 5951e40eb5d..0d56fa405a5 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -1135,11 +1135,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 3e960c249a4..946100cd6a4 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -1223,11 +1223,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 8540ab05ef4..6ebf5b27c5d 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -1166,11 +1166,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index d0a7b94a9d2..d0d18cd069f 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -1263,11 +1263,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml index 4e593aa9f8f..1465ff4a485 100644 --- a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml +++ b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml @@ -1113,11 +1113,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index e5a7c62bdfc..addc561a8d6 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -1385,11 +1385,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index 579b6ff1ed8..8c98f1a5528 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -1188,11 +1188,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-harness-experiment-proposer.lock.yml b/.github/workflows/daily-harness-experiment-proposer.lock.yml index 59bda65e45f..41225b4cdec 100644 --- a/.github/workflows/daily-harness-experiment-proposer.lock.yml +++ b/.github/workflows/daily-harness-experiment-proposer.lock.yml @@ -1250,11 +1250,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index e3dc3607f1c..71c80483c97 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -1306,11 +1306,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml index 5ee5d85350a..022127b08be 100644 --- a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml +++ b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml @@ -1275,11 +1275,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-storify.lock.yml b/.github/workflows/daily-storify.lock.yml index df2f7420edf..b65ac1b1e5f 100644 --- a/.github/workflows/daily-storify.lock.yml +++ b/.github/workflows/daily-storify.lock.yml @@ -1251,11 +1251,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 0b12096924c..bc4a90d9b70 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -1223,11 +1223,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index cea8afe7c9a..99b662c4dc6 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -1937,11 +1937,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 6d335cf4f30..d7386313d50 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -1193,11 +1193,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/eslint-refiner.lock.yml b/.github/workflows/eslint-refiner.lock.yml index 85b2af2dc84..2f3a01b0e9c 100644 --- a/.github/workflows/eslint-refiner.lock.yml +++ b/.github/workflows/eslint-refiner.lock.yml @@ -1217,11 +1217,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 55a2552b706..8c9d8349982 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -1235,11 +1235,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 5fbb72f1774..373c90be337 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -1584,11 +1584,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 031b03ca149..9601e415614 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -1142,11 +1142,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 8eeace09d14..00c129d7734 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -1271,11 +1271,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index e94206bbaed..76e55779c89 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -1407,11 +1407,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index fb38bdaa362..fbd0b27f971 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -1228,11 +1228,8 @@ jobs: const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions'); const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs')); setupGlobals(core, github, context, exec, io, getOctokit); - const { filterIneligibleMemoryFiles } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); - const memoryDir = process.env.MEMORY_DIR || ''; - const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]'); - const fileGlobFilter = process.env.FILE_GLOB_FILTER || ''; - filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); + await main(); - name: Upload repo-memory artifact (default) if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/actions/setup/js/memory_file_eligibility.cjs b/actions/setup/js/memory_file_eligibility.cjs index 82d01791717..f0c41a60093 100644 --- a/actions/setup/js/memory_file_eligibility.cjs +++ b/actions/setup/js/memory_file_eligibility.cjs @@ -118,4 +118,18 @@ function filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilte return { kept, removed }; } -module.exports = { compileFileGlobPatterns, isMemoryFileEligible, filterIneligibleMemoryFiles }; +/** + * Entry point used by the compiler's "Filter memory files" step (via + * generateGitHubScriptWithRequire). Reads MEMORY_DIR, ALLOWED_EXTENSIONS + * (JSON array), and FILE_GLOB_FILTER from the environment and filters the + * memory directory in place, relying on the `core` global set up by + * setup_globals.cjs. + */ +function main() { + const memoryDir = process.env.MEMORY_DIR || ""; + const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || "[]"); + const fileGlobFilter = process.env.FILE_GLOB_FILTER || ""; + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); +} + +module.exports = { compileFileGlobPatterns, isMemoryFileEligible, filterIneligibleMemoryFiles, main }; diff --git a/actions/setup/js/push_repo_memory.test.cjs b/actions/setup/js/push_repo_memory.test.cjs index 45bf538b6ab..2961a28f575 100644 --- a/actions/setup/js/push_repo_memory.test.cjs +++ b/actions/setup/js/push_repo_memory.test.cjs @@ -1617,7 +1617,7 @@ describe("push_repo_memory.cjs - allowed-extensions persistence filter (regressi // Allowed-extensions and file-glob must both be applied as persistence filters // via the shared eligibility helper, before size/count/patch/custom validation. expect(scriptContent).toContain("isMemoryFileEligible(relativeFilePath, allowedExtensions, compiledPatterns)"); - expect(scriptContent).toContain("require(\"./memory_file_eligibility.cjs\")"); + expect(scriptContent).toContain('require("./memory_file_eligibility.cjs")'); // The old behavior — validating the whole source directory after scanning and // hard-failing the job (e.g. for a leftover "notes.json.new" file) — must be gone. @@ -1636,7 +1636,6 @@ describe("push_repo_memory.cjs - allowed-extensions persistence filter (regressi }); }); - // ────────────────────────────────────────────────────────────────────────────── // Signed-commit push tests // Verifies that push_repo_memory delegates to pushSignedCommits (GraphQL-based diff --git a/actions/setup/js/validate_memory_step.cjs b/actions/setup/js/validate_memory_step.cjs index 7074201281b..df38199c91d 100644 --- a/actions/setup/js/validate_memory_step.cjs +++ b/actions/setup/js/validate_memory_step.cjs @@ -1,7 +1,7 @@ // @ts-check const { formatJSONFiles, runCustomMemoryValidation, writeValidationMarker, clearValidationMarker } = require("./memory_custom_validation.cjs"); -const { validateMemoryFiles } = require("./validate_memory_files.cjs"); +const { filterIneligibleMemoryFiles } = require("./memory_file_eligibility.cjs"); /** * @param {{ error: (message: string) => void, info: (message: string) => void, setFailed: (message: string) => void }} core @@ -22,12 +22,12 @@ function validateMemoryStep(core, options) { clearValidationMarker(options.kind, memoryId); } + // Allowed-extensions (and file-glob, when present) are persistence filters, not hard + // failures: ineligible files are logged and removed here so that custom validation, + // artifact upload/save, and any downstream push all see the same effective file set. + // This mirrors the filtering applied for repo-memory before this step runs. if (allowedExtensions.length > 0) { - const result = validateMemoryFiles(memoryDir, options.kind, allowedExtensions, core); - if (!result.valid) { - core.setFailed(`Storage file type validation failed: Found ${result.invalidFiles.length} file(s) with invalid extensions. Only ${allowedExtensions.join(", ")} are allowed.`); - failed = true; - } + filterIneligibleMemoryFiles(memoryDir, allowedExtensions, process.env.FILE_GLOB_FILTER || "", core); } if (options.formatJSON) { diff --git a/actions/setup/js/validate_memory_step.test.cjs b/actions/setup/js/validate_memory_step.test.cjs index aedc3d188ef..2e6e804b2d0 100644 --- a/actions/setup/js/validate_memory_step.test.cjs +++ b/actions/setup/js/validate_memory_step.test.cjs @@ -48,4 +48,24 @@ describe("validateMemoryStep", () => { expect(validateMemoryStep(core, { kind: "drive" })).toBe(true); }); + + it("filters (never hard-fails) disallowed-extension files uniformly across memory kinds", () => { + delete process.env.VALIDATION_SCRIPT_B64; + fs.writeFileSync(path.join(tempDir, "notes.json"), "{}"); + fs.writeFileSync(path.join(tempDir, "notes.json.new"), "ignored"); + const messages = []; + const core = { + info: message => messages.push(message), + error: message => messages.push(`error: ${message}`), + setFailed: message => messages.push(`failed: ${message}`), + }; + + for (const kind of ["repo", "cache", "drive"]) { + expect(validateMemoryStep(core, { kind })).toBe(true); + } + + expect(messages.some(m => m.startsWith("failed:"))).toBe(false); + expect(fs.existsSync(path.join(tempDir, "notes.json"))).toBe(true); + expect(fs.existsSync(path.join(tempDir, "notes.json.new"))).toBe(false); + }); }); diff --git a/pkg/workflow/drive_memory_test.go b/pkg/workflow/drive_memory_test.go index 361a4b021fd..1d467ee1a46 100644 --- a/pkg/workflow/drive_memory_test.go +++ b/pkg/workflow/drive_memory_test.go @@ -262,6 +262,47 @@ func TestDriveMemoryPersistenceWithoutValidationUsesDefaultSuccessCondition(t *t assert.NotContains(t, persist.String(), "if:") } +// TestDriveMemoryValidationConfigAndGeneratedSteps mirrors +// TestCacheMemoryValidationConfigAndGeneratedSteps and +// TestRepoMemoryValidationConfigAndGeneratedSteps to confirm the script-based +// custom validation hook is wired uniformly for drive-memory too: parsed from +// config, passed through as VALIDATION_SCRIPT_B64 to validate_memory_step.cjs, +// and gating persistence on the validation step's outcome. +func TestDriveMemoryValidationConfigAndGeneratedSteps(t *testing.T) { + compiler := NewCompiler() + config, err := compiler.extractDriveMemoryConfig(&ToolsConfig{ + DriveMemory: &DriveMemoryToolConfig{Raw: []any{ + map[string]any{ + "id": "default", + "drive-name": "agent-state", + "validation": map[string]any{ + "script": "if (!fs.existsSync(path.join(memoryRoot, 'index.json'))) throw new Error('missing index');", + "timeout-minutes": 1, + }, + }, + }}, + }) + require.NoError(t, err) + require.NotNil(t, config) + require.Len(t, config.Drives, 1) + require.NotNil(t, config.Drives[0].Validation) + assert.Equal(t, 1, config.Drives[0].Validation.TimeoutMinutes) + + data := &WorkflowData{DriveMemoryConfig: config} + + var validation strings.Builder + generateDriveMemoryValidation(&validation, data) + validationYAML := validation.String() + assert.Contains(t, validationYAML, "Validate drive-memory file types (default)") + assert.Contains(t, validationYAML, "VALIDATION_SCRIPT_B64:") + assert.Contains(t, validationYAML, "validate_memory_step.cjs") + assert.Contains(t, validationYAML, "id: "+driveMemoryValidationStepID("default")) + + var persist strings.Builder + generateDriveMemoryPersistence(&persist, data, func(action string) string { return action + "@test-pin" }) + assert.Contains(t, persist.String(), "steps."+driveMemoryValidationStepID("default")+".outcome == 'success'") +} + func TestDriveMemoryRestorePreservesIntegrityLevel(t *testing.T) { compiler := NewCompiler() data := &WorkflowData{ diff --git a/pkg/workflow/repo_memory.go b/pkg/workflow/repo_memory.go index f99c383fdb7..81ebdf3290b 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -354,96 +354,105 @@ func generateRepoMemoryArtifactUpload(builder *strings.Builder, data *WorkflowDa builder.WriteString(" # Upload repo memory as artifacts for push job\n") for _, memory := range data.RepoMemoryConfig.Memories { - // Determine the memory directory memoryDir := constants.TmpRepoMemoryDir + memory.ID - - // Sanitize memory ID for artifact naming (remove hyphens, lowercase) sanitizedID := SanitizeWorkflowIDForCacheKey(memory.ID) - - // Determine the label for step names memoryLabel := "repo-memory" if memory.Wiki { memoryLabel = "wiki-memory" } - // Step: Sanitize filenames before upload to prevent artifact upload failures. - // GitHub Actions artifacts are stored on NTFS-compatible filesystems, so filenames - // must not contain: ? : * | < > " (among other characters). - // The agent may create files with these characters (e.g. "Can-we-have-a-PR?.md"), - // which causes the upload-artifact action to fail with a hard error. - // The script uses git commands (git mv for tracked files, mv for untracked) since - // repo-memory is backed by a git working tree. - fmt.Fprintf(builder, " - name: Sanitize %s filenames (%s)\n", memoryLabel, memory.ID) - builder.WriteString(" if: always()\n") - builder.WriteString(" continue-on-error: true\n") - builder.WriteString(" env:\n") - fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) - builder.WriteString(" run: bash \"${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh\"\n") - - // Step: Filter out files that are ineligible for persistence (disallowed - // extensions or non-matching file-glob patterns) before validation and - // upload. Allowed-extensions and file-glob are persistence filters: ineligible - // files are logged and removed here so that custom validation, the uploaded - // artifact, and the downstream push all see the same effective file set. - if len(memory.AllowedExtensions) > 0 || len(memory.FileGlob) > 0 { - allowedExtsJSON, _ := json.Marshal(memory.AllowedExtensions) //nolint:jsonmarshalignoredeerror // marshaling a string slice cannot fail - fmt.Fprintf(builder, " - name: Filter %s files (%s)\n", memoryLabel, memory.ID) - builder.WriteString(" if: always()\n") - fmt.Fprintf(builder, " uses: %s\n", getActionPin("actions/github-script")) - builder.WriteString(" env:\n") - fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) - fmt.Fprintf(builder, " ALLOWED_EXTENSIONS: '%s'\n", allowedExtsJSON) - if len(memory.FileGlob) > 0 { - fmt.Fprintf(builder, " FILE_GLOB_FILTER: \"%s\"\n", strings.Join(memory.FileGlob, " ")) - } - builder.WriteString(" with:\n") - builder.WriteString(" script: |\n") - builder.WriteString(" const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');\n") - builder.WriteString(" setupGlobals(core, github, context, exec, io, getOctokit);\n") - builder.WriteString(" const { filterIneligibleMemoryFiles } = require('${{ runner.temp }}/gh-aw/actions/memory_file_eligibility.cjs');\n") - builder.WriteString(" const memoryDir = process.env.MEMORY_DIR || '';\n") - builder.WriteString(" const allowedExtensions = JSON.parse(process.env.ALLOWED_EXTENSIONS || '[]');\n") - builder.WriteString(" const fileGlobFilter = process.env.FILE_GLOB_FILTER || '';\n") - builder.WriteString(" filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core);\n") - } + generateRepoMemorySanitizeFilenamesStep(builder, memory, memoryDir, memoryLabel) + generateRepoMemoryFilterFilesStep(builder, memory, memoryDir, memoryLabel) + validationStepID := generateRepoMemoryCustomValidationStep(builder, memory, memoryDir, memoryLabel) + generateRepoMemoryUploadArtifactStep(builder, memory, memoryDir, memoryLabel, sanitizedID, prefix, validationStepID, pinAction) + } +} - validationStepID := repoMemoryValidationStepID(memory.ID) - if memory.Validation != nil { - fmt.Fprintf(builder, " - name: Validate %s domain content (%s)\n", memoryLabel, memory.ID) - fmt.Fprintf(builder, " id: %s\n", validationStepID) - builder.WriteString(" if: always()\n") - fmt.Fprintf(builder, " uses: %s\n", getActionPin("actions/github-script")) - builder.WriteString(" env:\n") - fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) - fmt.Fprintf(builder, " MEMORY_ID: %s\n", memory.ID) - fmt.Fprintf(builder, " VALIDATION_SCRIPT_B64: %s\n", memoryValidationScriptBase64(memory.Validation)) - fmt.Fprintf(builder, " VALIDATION_TIMEOUT_SECONDS: %d\n", memoryValidationTimeoutSeconds(memory.Validation)) - if memory.FormatJSON { - builder.WriteString(" FORMAT_JSON: 'true'\n") - } +// generateRepoMemorySanitizeFilenamesStep emits the step that renames files with characters +// unsafe for artifact upload before validation and upload. +// GitHub Actions artifacts are stored on NTFS-compatible filesystems, so filenames +// must not contain: ? : * | < > " (among other characters). +// The agent may create files with these characters (e.g. "Can-we-have-a-PR?.md"), +// which causes the upload-artifact action to fail with a hard error. +// The script uses git commands (git mv for tracked files, mv for untracked) since +// repo-memory is backed by a git working tree. +func generateRepoMemorySanitizeFilenamesStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel string) { + fmt.Fprintf(builder, " - name: Sanitize %s filenames (%s)\n", memoryLabel, memory.ID) + builder.WriteString(" if: always()\n") + builder.WriteString(" continue-on-error: true\n") + builder.WriteString(" env:\n") + fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) + builder.WriteString(" run: bash \"${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh\"\n") +} - builder.WriteString(" with:\n") - builder.WriteString(" script: |\n") - builder.WriteString(" const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');\n") - builder.WriteString(" setupGlobals(core, github, context, exec, io, getOctokit);\n") - builder.WriteString(" const { validateMemoryStep } = require('${{ runner.temp }}/gh-aw/actions/validate_memory_step.cjs');\n") - builder.WriteString(" validateMemoryStep(core, { kind: 'repo', formatJSON: process.env.FORMAT_JSON === 'true', requireValidationScript: true });\n") - } +// generateRepoMemoryFilterFilesStep emits the step that filters out files ineligible for +// persistence (disallowed extensions or non-matching file-glob patterns) before validation +// and upload. Allowed-extensions and file-glob are persistence filters: ineligible files +// are logged and removed here so that custom validation, the uploaded artifact, and the +// downstream push all see the same effective file set. +func generateRepoMemoryFilterFilesStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel string) { + if len(memory.AllowedExtensions) == 0 && len(memory.FileGlob) == 0 { + return + } + allowedExtsJSON, _ := json.Marshal(memory.AllowedExtensions) //nolint:jsonmarshalignoredeerror // marshaling a string slice cannot fail + fmt.Fprintf(builder, " - name: Filter %s files (%s)\n", memoryLabel, memory.ID) + builder.WriteString(" if: always()\n") + fmt.Fprintf(builder, " uses: %s\n", getActionPin("actions/github-script")) + builder.WriteString(" env:\n") + fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) + fmt.Fprintf(builder, " ALLOWED_EXTENSIONS: '%s'\n", allowedExtsJSON) + if len(memory.FileGlob) > 0 { + fmt.Fprintf(builder, " FILE_GLOB_FILTER: \"%s\"\n", strings.Join(memory.FileGlob, " ")) + } + builder.WriteString(" with:\n") + builder.WriteString(" script: |\n") + builder.WriteString(generateGitHubScriptWithRequire("memory_file_eligibility.cjs")) +} - // Step: Upload repo-memory directory as artifact - fmt.Fprintf(builder, " - name: Upload %s artifact (%s)\n", memoryLabel, memory.ID) - if memory.Validation != nil { - fmt.Fprintf(builder, " if: always() && steps.%s.outcome == 'success'\n", validationStepID) - } else { - builder.WriteString(" if: always()\n") - } - fmt.Fprintf(builder, " uses: %s\n", pinAction("actions/upload-artifact")) - builder.WriteString(" with:\n") - fmt.Fprintf(builder, " name: %srepo-memory-%s\n", prefix, sanitizedID) - fmt.Fprintf(builder, " path: %s\n", memoryDir) - builder.WriteString(" retention-days: 1\n") - builder.WriteString(" if-no-files-found: ignore\n") +// generateRepoMemoryCustomValidationStep emits the optional custom-validation step and +// returns its step ID (used to gate the subsequent upload step), or "" when no custom +// validation is configured for this memory. +func generateRepoMemoryCustomValidationStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel string) string { + if memory.Validation == nil { + return "" + } + validationStepID := repoMemoryValidationStepID(memory.ID) + fmt.Fprintf(builder, " - name: Validate %s domain content (%s)\n", memoryLabel, memory.ID) + fmt.Fprintf(builder, " id: %s\n", validationStepID) + builder.WriteString(" if: always()\n") + fmt.Fprintf(builder, " uses: %s\n", getActionPin("actions/github-script")) + builder.WriteString(" env:\n") + fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) + fmt.Fprintf(builder, " MEMORY_ID: %s\n", memory.ID) + fmt.Fprintf(builder, " VALIDATION_SCRIPT_B64: %s\n", memoryValidationScriptBase64(memory.Validation)) + fmt.Fprintf(builder, " VALIDATION_TIMEOUT_SECONDS: %d\n", memoryValidationTimeoutSeconds(memory.Validation)) + if memory.FormatJSON { + builder.WriteString(" FORMAT_JSON: 'true'\n") + } + builder.WriteString(" with:\n") + builder.WriteString(" script: |\n") + builder.WriteString(" const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');\n") + builder.WriteString(" setupGlobals(core, github, context, exec, io, getOctokit);\n") + builder.WriteString(" const { validateMemoryStep } = require('${{ runner.temp }}/gh-aw/actions/validate_memory_step.cjs');\n") + builder.WriteString(" validateMemoryStep(core, { kind: 'repo', formatJSON: process.env.FORMAT_JSON === 'true', requireValidationScript: true });\n") + return validationStepID +} + +// generateRepoMemoryUploadArtifactStep emits the step that uploads the repo-memory +// directory as an artifact, gated on the custom-validation step's outcome when configured. +func generateRepoMemoryUploadArtifactStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel, sanitizedID, prefix, validationStepID string, pinAction func(string) string) { + fmt.Fprintf(builder, " - name: Upload %s artifact (%s)\n", memoryLabel, memory.ID) + if validationStepID != "" { + fmt.Fprintf(builder, " if: always() && steps.%s.outcome == 'success'\n", validationStepID) + } else { + builder.WriteString(" if: always()\n") } + fmt.Fprintf(builder, " uses: %s\n", pinAction("actions/upload-artifact")) + builder.WriteString(" with:\n") + fmt.Fprintf(builder, " name: %srepo-memory-%s\n", prefix, sanitizedID) + fmt.Fprintf(builder, " path: %s\n", memoryDir) + builder.WriteString(" retention-days: 1\n") + builder.WriteString(" if-no-files-found: ignore\n") } func repoMemoryValidationStepID(memoryID string) string { From b11fdf51fcd844b1b7926ac922126f05f26831e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 06:20:07 +0000 Subject: [PATCH 5/7] repo-memory: consistent filtering across preflight, agent validation, and push; fix lint debt Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- .../agent-performance-analyzer.lock.yml | 5 +- .../workflows/agentic-token-audit.lock.yml | 5 +- .../agentic-token-optimizer.lock.yml | 5 +- .github/workflows/audit-workflows.lock.yml | 5 +- .../workflows/copilot-agent-analysis.lock.yml | 5 +- .../copilot-centralization-optimizer.lock.yml | 5 +- .../copilot-cli-deep-research.lock.yml | 5 +- .../copilot-pr-nlp-analysis.lock.yml | 5 +- .../copilot-pr-prompt-analysis.lock.yml | 5 +- .../copilot-session-insights.lock.yml | 5 +- ...daily-awf-spec-compiler-surfacing.lock.yml | 5 +- .../workflows/daily-cli-performance.lock.yml | 5 +- .../daily-formal-spec-verifier.lock.yml | 5 +- ...daily-harness-experiment-proposer.lock.yml | 5 +- .github/workflows/daily-news.lock.yml | 5 +- .../daily-safeoutputs-git-simulator.lock.yml | 5 +- .github/workflows/daily-storify.lock.yml | 5 +- .../daily-testify-uber-super-expert.lock.yml | 5 +- .github/workflows/deep-report.lock.yml | 5 +- .github/workflows/delight.lock.yml | 5 +- .github/workflows/eslint-refiner.lock.yml | 5 +- .github/workflows/metrics-collector.lock.yml | 5 +- .github/workflows/pr-triage-agent.lock.yml | 5 +- .../workflows/security-compliance.lock.yml | 5 +- .github/workflows/sergo.lock.yml | 5 +- .github/workflows/smoke-ci.lock.yml | 5 +- .../workflow-health-manager.lock.yml | 5 +- actions/setup/js/memory_file_eligibility.cjs | 9 +- .../setup/js/memory_file_eligibility.test.cjs | 9 + actions/setup/js/push_repo_memory.cjs | 14 +- actions/setup/js/push_repo_memory.test.cjs | 20 + actions/setup/js/safe_outputs_handlers.cjs | 14 + .../setup/js/safe_outputs_handlers.test.cjs | 31 ++ actions/setup/setup.sh | 1 + pkg/workflow/repo_memory.go | 80 +++- pkg/workflow/repo_memory_test.go | 72 +++ .../safe_outputs_config_generation.go | 417 ++++++++++-------- .../safe_outputs_config_generation_test.go | 54 +++ 38 files changed, 592 insertions(+), 264 deletions(-) diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 9573b94f971..618270c529a 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -671,7 +671,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":10},\"create_discussion\":{\"category\":\"audits\",\"expires\":24,\"fallback_to_issue\":true,\"max\":1},\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"cookie\"],\"max\":5},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":10},\"create_discussion\":{\"category\":\"audits\",\"expires\":24,\"fallback_to_issue\":true,\"max\":1},\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"cookie\"],\"max\":5},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1342,6 +1342,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1357,7 +1358,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/agentic-token-audit.lock.yml b/.github/workflows/agentic-token-audit.lock.yml index fd062bb83fa..aa73fbfe8b9 100644 --- a/.github/workflows/agentic-token-audit.lock.yml +++ b/.github/workflows/agentic-token-audit.lock.yml @@ -644,7 +644,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"max\":1,\"title_prefix\":\"[agentic-token-audit] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"max\":1,\"title_prefix\":\"[agentic-token-audit] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" GITHUB_WORKFLOW: ${{ github.workflow }} with: script: | @@ -1224,6 +1224,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1239,7 +1240,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/agentic-token-optimizer.lock.yml b/.github/workflows/agentic-token-optimizer.lock.yml index a3a35d7ab55..641bcc865cd 100644 --- a/.github/workflows/agentic-token-optimizer.lock.yml +++ b/.github/workflows/agentic-token-optimizer.lock.yml @@ -592,7 +592,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":168,\"max\":1,\"title_prefix\":\"[agentic-token-optimizer] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":168,\"max\":1,\"title_prefix\":\"[agentic-token-optimizer] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1134,6 +1134,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1149,7 +1150,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index b93efc8ebb2..3b8d5890b31 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -748,7 +748,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[audit-workflows] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":3,\"max-size\":10240}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[audit-workflows] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":3,\"max-size\":10240}}" GITHUB_WORKFLOW: ${{ github.workflow }} with: script: | @@ -1387,6 +1387,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1402,7 +1403,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 62c34055844..45814c8426d 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -694,7 +694,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[copilot-agent-analysis] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[copilot-agent-analysis] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1278,6 +1278,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1293,7 +1294,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/copilot-centralization-optimizer.lock.yml b/.github/workflows/copilot-centralization-optimizer.lock.yml index bc53a44ae56..5c7e4ba95ba 100644 --- a/.github/workflows/copilot-centralization-optimizer.lock.yml +++ b/.github/workflows/copilot-centralization-optimizer.lock.yml @@ -603,7 +603,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":720,\"labels\":[\"report\",\"ai-optimization\"],\"max\":1,\"title_prefix\":\"[copilot-centralization] \"},\"create_report_incomplete_issue\":{},\"max_bot_mentions\":1,\"mentions\":{\"enabled\":false},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":720,\"labels\":[\"report\",\"ai-optimization\"],\"max\":1,\"title_prefix\":\"[copilot-centralization] \"},\"create_report_incomplete_issue\":{},\"max_bot_mentions\":1,\"mentions\":{\"enabled\":false},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1176,6 +1176,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1191,7 +1192,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index 0d56fa405a5..3923c7eeaea 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -570,7 +570,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":24,\"max\":1,\"title_prefix\":\"[copilot-cli-research] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":204800,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":24,\"max\":1,\"title_prefix\":\"[copilot-cli-research] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":204800,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1123,6 +1123,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1138,7 +1139,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/copilot-pr-nlp-analysis.lock.yml b/.github/workflows/copilot-pr-nlp-analysis.lock.yml index 946100cd6a4..61318a101fc 100644 --- a/.github/workflows/copilot-pr-nlp-analysis.lock.yml +++ b/.github/workflows/copilot-pr-nlp-analysis.lock.yml @@ -657,7 +657,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[nlp-analysis] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[nlp-analysis] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" GITHUB_WORKFLOW: ${{ github.workflow }} with: script: | @@ -1211,6 +1211,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1226,7 +1227,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index 6ebf5b27c5d..3ee12d3f50b 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -617,7 +617,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[prompt-analysis] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[prompt-analysis] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1154,6 +1154,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1169,7 +1170,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index d0d18cd069f..ed7911ee67d 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -656,7 +656,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[copilot-session-insights] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[copilot-session-insights] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" GITHUB_WORKFLOW: ${{ github.workflow }} with: script: | @@ -1251,6 +1251,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1266,7 +1267,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml index 1465ff4a485..15d84336ec3 100644 --- a/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml +++ b/.github/workflows/daily-awf-spec-compiler-surfacing.lock.yml @@ -596,7 +596,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":168,\"labels\":[\"automation\",\"awf\",\"compiler\",\"specifications\"],\"max\":1,\"title_prefix\":\"[awf-feature-surfacing] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":65536,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"expires\":168,\"labels\":[\"automation\",\"awf\",\"compiler\",\"specifications\"],\"max\":1,\"title_prefix\":\"[awf-feature-surfacing] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\".json .md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":65536,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1101,6 +1101,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1116,7 +1117,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index addc561a8d6..8afa6072775 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -641,7 +641,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":5},\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[daily-cli-performance] \"},\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"performance\",\"automation\",\"cookie\"],\"max\":3,\"title_prefix\":\"[performance] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":131072,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":5},\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[daily-cli-performance] \"},\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"performance\",\"automation\",\"cookie\"],\"max\":3,\"title_prefix\":\"[performance] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.txt\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":131072,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1373,6 +1373,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1388,7 +1389,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index 8c98f1a5528..45be25a23f2 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -599,7 +599,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"expires\":168,\"labels\":[\"automation\",\"formal-verification\",\"testing\",\"specifications\"],\"max\":1,\"title_prefix\":\"[formal-spec] \"},\"create_report_incomplete_issue\":{},\"max_bot_mentions\":1,\"mentions\":{\"enabled\":false},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":65536,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"assignees\":[\"copilot\"],\"expires\":168,\"labels\":[\"automation\",\"formal-verification\",\"testing\",\"specifications\"],\"max\":1,\"title_prefix\":\"[formal-spec] \"},\"create_report_incomplete_issue\":{},\"max_bot_mentions\":1,\"mentions\":{\"enabled\":false},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.md *.json\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":65536,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1176,6 +1176,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1191,7 +1192,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-harness-experiment-proposer.lock.yml b/.github/workflows/daily-harness-experiment-proposer.lock.yml index 41225b4cdec..31d0e2794c0 100644 --- a/.github/workflows/daily-harness-experiment-proposer.lock.yml +++ b/.github/workflows/daily-harness-experiment-proposer.lock.yml @@ -647,7 +647,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"expires\":168,\"labels\":[\"automation\",\"experiment-proposal\",\"harness\",\"needs-manual-patch\"],\"max\":1,\"title_prefix\":\"[harness-experiment-proposal] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"expires\":168,\"labels\":[\"automation\",\"experiment-proposal\",\"harness\",\"needs-manual-patch\"],\"max\":1,\"title_prefix\":\"[harness-experiment-proposal] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1238,6 +1238,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1253,7 +1254,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 71c80483c97..73fcb2d5888 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -752,7 +752,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"daily-news\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"upload_artifact\":{\"max-size-bytes\":104857600,\"max-uploads\":3,\"retention-days\":30,\"skip-archive\":true},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"daily-news\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"upload_artifact\":{\"max-size-bytes\":104857600,\"max-uploads\":3,\"retention-days\":30,\"skip-archive\":true},\"upload_asset\":{\"allowed-exts\":[\".png\",\".jpg\",\".jpeg\",\".svg\"],\"branch\":\"assets/${GITHUB_WORKFLOW}\",\"max\":5,\"max-size\":10240}}" GITHUB_WORKFLOW: ${{ github.workflow }} with: script: | @@ -1294,6 +1294,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1309,7 +1310,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml index 022127b08be..32bcb10dd1a 100644 --- a/.github/workflows/daily-safeoutputs-git-simulator.lock.yml +++ b/.github/workflows/daily-safeoutputs-git-simulator.lock.yml @@ -596,7 +596,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":false,\"deduplicate_by_title\":3,\"labels\":[\"git-simulator\",\"safe-outputs\",\"automated\"],\"max\":10,\"title_prefix\":\"[git-sim] \"},\"create_pull_request\":{\"allowed_files\":[\"sim/**\",\"stuff.md\",\"history.md\"],\"draft\":true,\"expires\":24,\"if_no_changes\":\"warn\",\"labels\":[\"git-sim-probe\",\"automated\"],\"max\":1,\"max_patch_files\":200,\"max_patch_size\":5120,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[git-sim] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":204800,\"max_patch_size\":10240}]},\"push_to_pull_request_branch\":{\"allowed_files\":[\"sim/**\",\"stuff.md\",\"history.md\"],\"if_no_changes\":\"ignore\",\"max_patch_size\":5120,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"required_labels\":[\"git-sim-probe\",\"automated\"],\"target\":\"*\",\"title_prefix\":\"[git-sim] \"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"close_older_issues\":false,\"deduplicate_by_title\":3,\"labels\":[\"git-simulator\",\"safe-outputs\",\"automated\"],\"max\":10,\"title_prefix\":\"[git-sim] \"},\"create_pull_request\":{\"allowed_files\":[\"sim/**\",\"stuff.md\",\"history.md\"],\"draft\":true,\"expires\":24,\"if_no_changes\":\"warn\",\"labels\":[\"git-sim-probe\",\"automated\"],\"max\":1,\"max_patch_files\":200,\"max_patch_size\":5120,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[git-sim] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"allowed_extensions\":[\".json\",\".md\"],\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":204800,\"max_patch_size\":10240}]},\"push_to_pull_request_branch\":{\"allowed_files\":[\"sim/**\",\"stuff.md\",\"history.md\"],\"if_no_changes\":\"ignore\",\"max_patch_size\":5120,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"CLAUDE.md\",\"AGENTS.md\"],\"required_labels\":[\"git-sim-probe\",\"automated\"],\"target\":\"*\",\"title_prefix\":\"[git-sim] \"},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1264,6 +1264,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1278,7 +1279,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-storify.lock.yml b/.github/workflows/daily-storify.lock.yml index b65ac1b1e5f..00a6d401470 100644 --- a/.github/workflows/daily-storify.lock.yml +++ b/.github/workflows/daily-storify.lock.yml @@ -653,7 +653,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":48,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[storify] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":48,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[storify] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"storify/state.json storify/episodes.jsonl storify/loops.jsonl\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1239,6 +1239,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1254,7 +1255,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index bc4a90d9b70..19933edf23b 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -594,7 +594,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"expires\":48,\"labels\":[\"testing\",\"code-quality\",\"automated-analysis\",\"cookie\"],\"max\":1,\"title_prefix\":\"[testify-expert] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":51200,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"expires\":48,\"labels\":[\"testing\",\"code-quality\",\"automated-analysis\",\"cookie\"],\"max\":1,\"title_prefix\":\"[testify-expert] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.txt\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":51200,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1211,6 +1211,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1226,7 +1227,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 99b662c4dc6..51030f80b3f 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -928,7 +928,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":3},\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":168,\"fallback_to_issue\":true,\"max\":1},\"create_issue\":{\"deduplicate_by_title\":28,\"expires\":48,\"group\":true,\"labels\":[\"automation\",\"improvement\",\"quick-win\",\"cookie\",\"code-quality\",\"task-mining\"],\"max\":7,\"title_prefix\":\"[deep-report] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":1048576,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"upload_artifact\":{\"max-size-bytes\":104857600,\"max-uploads\":1,\"retention-days\":30}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":3},\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":168,\"fallback_to_issue\":true,\"max\":1},\"create_issue\":{\"deduplicate_by_title\":28,\"expires\":48,\"group\":true,\"labels\":[\"automation\",\"improvement\",\"quick-win\",\"cookie\",\"code-quality\",\"task-mining\"],\"max\":7,\"title_prefix\":\"[deep-report] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.md *.json\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":1048576,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"upload_artifact\":{\"max-size-bytes\":104857600,\"max-uploads\":1,\"retention-days\":30}}" with: script: | const path = require('path'); @@ -1925,6 +1925,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1940,7 +1941,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index d7386313d50..a63ed5adb4a 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -574,7 +574,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[delight] \"},\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"delight\",\"cookie\"],\"max\":2},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":72,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[delight] \"},\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"delight\",\"cookie\"],\"max\":2},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl *.csv *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1181,6 +1181,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1196,7 +1197,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/eslint-refiner.lock.yml b/.github/workflows/eslint-refiner.lock.yml index 2f3a01b0e9c..15d30b907ed 100644 --- a/.github/workflows/eslint-refiner.lock.yml +++ b/.github/workflows/eslint-refiner.lock.yml @@ -575,7 +575,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[eslint-refiner] \"},\"create_issue\":{\"expires\":168,\"labels\":[\"eslint\",\"cookie\"],\"max\":3},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[eslint-refiner] \"},\"create_issue\":{\"expires\":168,\"labels\":[\"eslint\",\"cookie\"],\"max\":3},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1205,6 +1205,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1220,7 +1221,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 8c9d8349982..76930777a71 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -625,7 +625,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"labels\":[\"metrics-collector\"],\"max\":1,\"title_prefix\":\"[metrics-collector]\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":131072}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"labels\":[\"metrics-collector\"],\"max\":1,\"title_prefix\":\"[metrics-collector]\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"metrics/**\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":131072}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1223,6 +1223,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1238,7 +1239,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/pr-triage-agent.lock.yml b/.github/workflows/pr-triage-agent.lock.yml index 373c90be337..a87ae93cf06 100644 --- a/.github/workflows/pr-triage-agent.lock.yml +++ b/.github/workflows/pr-triage-agent.lock.yml @@ -591,7 +591,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"max\":50},\"add_labels\":{\"max\":100},\"create_check_run\":{\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"expires\":24,\"labels\":[\"automation\",\"pr-triage-report\"],\"max\":1,\"title_prefix\":\"[PR Triage Report] \"},\"create_pull_request_review_comment\":{\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"max\":10,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"submit_pull_request_review\":{\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"max\":1}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"max\":50},\"add_labels\":{\"max\":100},\"create_check_run\":{\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"expires\":24,\"labels\":[\"automation\",\"pr-triage-report\"],\"max\":1,\"title_prefix\":\"[PR Triage Report] \"},\"create_pull_request_review_comment\":{\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"max\":10,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{},\"submit_pull_request_review\":{\"data_enabled\":true,\"data_schema\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"category\":{\"type\":\"string\"},\"pr_number\":{\"type\":\"integer\"},\"risk\":{\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"pr_number\",\"risk\"],\"type\":\"object\"},\"max\":1}}" with: script: | const path = require('path'); @@ -1572,6 +1572,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1587,7 +1588,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/security-compliance.lock.yml b/.github/workflows/security-compliance.lock.yml index 9601e415614..05f18912d37 100644 --- a/.github/workflows/security-compliance.lock.yml +++ b/.github/workflows/security-compliance.lock.yml @@ -586,7 +586,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"security\",\"campaign-tracker\",\"cookie\"],\"max\":100},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_issue\":{\"expires\":48,\"group\":true,\"labels\":[\"security\",\"campaign-tracker\",\"cookie\"],\"max\":100},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"security-compliance-*/**\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1130,6 +1130,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1145,7 +1146,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 00c129d7734..fb3f056b12e 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -592,7 +592,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[sergo] \"},\"create_issue\":{\"expires\":168,\"labels\":[\"sergo\",\"cookie\"],\"max\":3},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"create_discussion\":{\"category\":\"audits\",\"close_older_discussions\":true,\"expires\":24,\"fallback_to_issue\":true,\"max\":1,\"title_prefix\":\"[sergo] \"},\"create_issue\":{\"expires\":168,\"labels\":[\"sergo\",\"cookie\"],\"max\":3},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.jsonl\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"report_incomplete\":{}}" with: script: | const path = require('path'); @@ -1259,6 +1259,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1274,7 +1275,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/smoke-ci.lock.yml b/.github/workflows/smoke-ci.lock.yml index 76e55779c89..724764bd369 100644 --- a/.github/workflows/smoke-ci.lock.yml +++ b/.github/workflows/smoke-ci.lock.yml @@ -668,7 +668,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"comment_memory\":{\"max\":1,\"memory_id\":\"default\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-ci-memory-safe-outputs\",\"labels\":[\"ai-generated\"],\"max\":1,\"title_prefix\":\"[smoke-ci] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"remove_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false,\"update_branch_stacks\":true}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"comment_memory\":{\"max\":1,\"memory_id\":\"default\"},\"create_issue\":{\"close_older_issues\":true,\"close_older_key\":\"smoke-ci-memory-safe-outputs\",\"labels\":[\"ai-generated\"],\"max\":1,\"title_prefix\":\"[smoke-ci] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":10240}]},\"remove_labels\":{\"allowed\":[\"ai-generated\"],\"max\":1},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":true,\"max\":1,\"target\":\"*\",\"update_branch\":false,\"update_branch_stacks\":true}}" with: script: | const path = require('path'); @@ -1395,6 +1395,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1410,7 +1411,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index fbd0b27f971..ab5773c0b91 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -579,7 +579,7 @@ jobs: env: GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw" GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}" - GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":15},\"create_issue\":{\"expires\":24,\"group\":true,\"labels\":[\"cookie\"],\"max\":10},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":5}}" + GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":15},\"create_issue\":{\"expires\":24,\"group\":true,\"labels\":[\"cookie\"],\"max\":10},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_repo_memory\":{\"memories\":[{\"dir\":\"/tmp/gh-aw/repo-memory/default\",\"file_glob\":\"*.json *.md\",\"id\":\"default\",\"max_file_count\":100,\"max_file_size\":102400,\"max_patch_size\":51200}]},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":5}}" with: script: | const path = require('path'); @@ -1216,6 +1216,7 @@ jobs: MEMORY_DIR: /tmp/gh-aw/repo-memory/default run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Filter repo-memory files (default) + id: filter_repo_memory_64656661756c74 if: always() uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -1231,7 +1232,7 @@ jobs: const { main } = require(path.join(actionsDir, 'memory_file_eligibility.cjs')); await main(); - name: Upload repo-memory artifact (default) - if: always() + if: always() && steps.filter_repo_memory_64656661756c74.outcome == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: repo-memory-default diff --git a/actions/setup/js/memory_file_eligibility.cjs b/actions/setup/js/memory_file_eligibility.cjs index f0c41a60093..2612d6d3684 100644 --- a/actions/setup/js/memory_file_eligibility.cjs +++ b/actions/setup/js/memory_file_eligibility.cjs @@ -8,9 +8,10 @@ const { globPatternToRegex } = require("./glob_pattern_helpers.cjs"); /** * Compile a space-separated FILE_GLOB_FILTER string into an array of RegExp patterns. - * Slashless patterns (e.g. "*.json") are matched at the root of a single memory - * subfolder (depth 1 only). Patterns that already contain "/" are matched against - * the full relative path unchanged. + * Patterns are matched against the file's path relative to the memory directory root. + * Slashless patterns (e.g. "*.json") match files directly at that root (depth 0), matching + * the documented FILE_GLOB_FILTER contract (e.g. "history.jsonl" at the memory directory + * root matches "*.jsonl"). Patterns containing "/" match the full relative path unchanged. * * @param {string} fileGlobFilter - Space-separated glob patterns (may be empty) * @returns {{ patternStrs: string[], compiledPatterns: RegExp[] }} @@ -20,7 +21,7 @@ function compileFileGlobPatterns(fileGlobFilter) { return { patternStrs: [], compiledPatterns: [] }; } const patternStrs = fileGlobFilter.trim().split(/\s+/).filter(Boolean); - const compiledPatterns = patternStrs.map(pattern => globPatternToRegex(pattern, { matchSubfolderRoot: !pattern.includes("/") })); + const compiledPatterns = patternStrs.map(pattern => globPatternToRegex(pattern)); return { patternStrs, compiledPatterns }; } diff --git a/actions/setup/js/memory_file_eligibility.test.cjs b/actions/setup/js/memory_file_eligibility.test.cjs index 4ac2c4f8abb..70952e44223 100644 --- a/actions/setup/js/memory_file_eligibility.test.cjs +++ b/actions/setup/js/memory_file_eligibility.test.cjs @@ -40,6 +40,15 @@ describe("memory_file_eligibility.cjs", () => { // Passes both filters expect(isMemoryFileEligible("sub/notes.json", [".json"], compiledPatterns).eligible).toBe(true); }); + + it("matches slashless patterns against root-level (depth 0) files, per the documented FILE_GLOB_FILTER contract", () => { + // FILE_GLOB_FILTER docs (push_repo_memory.cjs) document that a file at the memory + // directory root, e.g. "history.jsonl", is matched by the slashless pattern "*.jsonl". + const { compiledPatterns } = compileFileGlobPatterns("*.jsonl"); + expect(isMemoryFileEligible("history.jsonl", [], compiledPatterns).eligible).toBe(true); + // A nested file should not match a slashless pattern (single * doesn't cross directories). + expect(isMemoryFileEligible("sub/history.jsonl", [], compiledPatterns).eligible).toBe(false); + }); }); describe("compileFileGlobPatterns", () => { diff --git a/actions/setup/js/push_repo_memory.cjs b/actions/setup/js/push_repo_memory.cjs index c69bd11b9b0..02320ada776 100644 --- a/actions/setup/js/push_repo_memory.cjs +++ b/actions/setup/js/push_repo_memory.cjs @@ -9,7 +9,7 @@ const { getGitAuthEnv } = require("./git_auth_helpers.cjs"); const { execGitSync } = require("./git_helpers.cjs"); const { getStagedPatchDiffSizeBytes } = require("./git_patch_utils.cjs"); const { formatJSONFiles, runCustomMemoryValidation } = require("./memory_custom_validation.cjs"); -const { compileFileGlobPatterns, isMemoryFileEligible } = require("./memory_file_eligibility.cjs"); +const { compileFileGlobPatterns, filterIneligibleMemoryFiles, isMemoryFileEligible } = require("./memory_file_eligibility.cjs"); const { parseAllowedRepos, validateRepo } = require("./repo_helpers.cjs"); const { pushSignedCommits } = require("./push_signed_commits.cjs"); @@ -310,6 +310,18 @@ async function main() { const destMemoryPath = workspaceDir; core.info(`Destination directory: ${destMemoryPath}`); + // Remove any pre-existing files in the checked-out branch that no longer pass the + // current allowed-extensions/file-glob filters (e.g. left over from a prior run with + // different filter settings). Without this, formatJSONFiles/runCustomMemoryValidation + // below would still process stale ineligible files even though the newly copied files + // are already filtered. + if (allowedExtensions.length > 0 || fileGlobFilter) { + const { removed } = filterIneligibleMemoryFiles(destMemoryPath, allowedExtensions, fileGlobFilter, core); + if (removed.length > 0) { + core.info(`Removed ${removed.length} stale ineligible file(s) from existing branch content`); + } + } + // Recursively scan and collect files from artifact directory let filesToCopy = []; /** @type {Array<{path: string, reason: string}>} */ diff --git a/actions/setup/js/push_repo_memory.test.cjs b/actions/setup/js/push_repo_memory.test.cjs index 2961a28f575..b96e865b1e0 100644 --- a/actions/setup/js/push_repo_memory.test.cjs +++ b/actions/setup/js/push_repo_memory.test.cjs @@ -1634,6 +1634,26 @@ describe("push_repo_memory.cjs - allowed-extensions persistence filter (regressi expect(scriptContent).toContain("if (filesToCopy.length === 0)"); expect(scriptContent).toContain("No eligible files to copy from artifact"); }); + + it("filters stale ineligible files already present in the checked-out branch before format/validation (source check)", () => { + // Review feedback: after checking out an existing memory branch, files from prior + // runs that no longer match the current allowed-extensions/file-glob filters must + // not be left in destMemoryPath, since formatJSONFiles/runCustomMemoryValidation + // operate on the whole destMemoryPath, not just the newly-copied eligible files. + const nodeFs = require("fs"); + const nodePath = require("path"); + const scriptPath = nodePath.join(import.meta.dirname, "push_repo_memory.cjs"); + const scriptContent = nodeFs.readFileSync(scriptPath, "utf8"); + + expect(scriptContent).toContain("filterIneligibleMemoryFiles(destMemoryPath, allowedExtensions, fileGlobFilter, core)"); + // The stale-file filter must run before destMemoryPath is formatted/validated. + const filterIdx = scriptContent.indexOf("filterIneligibleMemoryFiles(destMemoryPath"); + const formatIdx = scriptContent.indexOf("formatJSONFiles(destMemoryPath, maxFileSize)"); + const validateIdx = scriptContent.indexOf("runCustomMemoryValidation({"); + expect(filterIdx).toBeGreaterThan(-1); + expect(filterIdx).toBeLessThan(formatIdx); + expect(filterIdx).toBeLessThan(validateIdx); + }); }); // ────────────────────────────────────────────────────────────────────────────── diff --git a/actions/setup/js/safe_outputs_handlers.cjs b/actions/setup/js/safe_outputs_handlers.cjs index 81b1bb45308..314eb846547 100644 --- a/actions/setup/js/safe_outputs_handlers.cjs +++ b/actions/setup/js/safe_outputs_handlers.cjs @@ -32,6 +32,7 @@ const { lstatGuard } = require("./symlink_guard.cjs"); const { validateValueAgainstSchema } = require("./mcp_scripts_validation.cjs"); const { resolveDataSchema } = require("./data_schema_normalizer.cjs"); const { clearValidationMarker, formatJSONFiles, runCustomMemoryValidation, writeValidationMarker } = require("./memory_custom_validation.cjs"); +const { filterIneligibleMemoryFiles } = require("./memory_file_eligibility.cjs"); /** PR event names used for target:triggering context validation across all safe-output handlers. */ const PR_EVENT_NAMES = new Set(["pull_request", "pull_request_target", "pull_request_review", "pull_request_review_comment"]); @@ -1786,6 +1787,8 @@ function createHandlers(server, appendSafeOutput, config = {}) { const maxFileSize = memoryConf.max_file_size || 10240; const maxPatchSize = memoryConf.max_patch_size || 10240; const maxFileCount = memoryConf.max_file_count || 100; + const allowedExtensions = Array.isArray(memoryConf.allowed_extensions) ? memoryConf.allowed_extensions : []; + const fileGlobFilter = typeof memoryConf.file_glob === "string" ? memoryConf.file_glob : ""; const validationConfig = memoryConf.validation || null; const validationScript = validationConfig && typeof validationConfig.script === "string" ? validationConfig.script : ""; const validationTimeoutSeconds = validationConfig && Number.isFinite(validationConfig.timeout) ? validationConfig.timeout : undefined; @@ -1804,6 +1807,17 @@ function createHandlers(server, appendSafeOutput, config = {}) { }; } + // Allowed-extensions and file-glob are persistence filters: ineligible files must be + // removed here too, before formatting/scanning/staging/custom-validation, so this + // preflight sees the same effective file set as the later filter/upload/push steps + // and never hard-fails on a file that would have been silently dropped downstream. + if (allowedExtensions.length > 0 || fileGlobFilter) { + const { removed } = filterIneligibleMemoryFiles(memoryDir, allowedExtensions, fileGlobFilter, core); + if (removed.length > 0) { + core.info(`push_repo_memory: ignored ${removed.length} ineligible file(s) before validation`); + } + } + clearValidationMarker("repo", memoryId); if (memoryConf.format_json === true) { diff --git a/actions/setup/js/safe_outputs_handlers.test.cjs b/actions/setup/js/safe_outputs_handlers.test.cjs index 86be9ca5fcd..90a83047887 100644 --- a/actions/setup/js/safe_outputs_handlers.test.cjs +++ b/actions/setup/js/safe_outputs_handlers.test.cjs @@ -3248,6 +3248,37 @@ describe("safe_outputs_handlers", () => { expect(data.error).toContain("3 files"); }); + it("should ignore ineligible files (disallowed extension) instead of hard-failing on file count", () => { + // Regression: the preflight must apply the same allowed-extensions/file-glob + // filtering as the agent-side filter step and the push job, so an ineligible + // file left in the memory directory does not cause a hard preflight failure. + const h = makeHandlersWithMemory({ max_file_count: 1, allowed_extensions: [".json"] }); + fs.mkdirSync(memoryDir, { recursive: true }); + initGitRepo(memoryDir); + fs.writeFileSync(path.join(memoryDir, "notes.json"), "{}"); + fs.writeFileSync(path.join(memoryDir, "notes.json.new"), "{}"); + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + expect(result.isError).toBeUndefined(); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("success"); + expect(fs.existsSync(path.join(memoryDir, "notes.json.new"))).toBe(false); + expect(fs.existsSync(path.join(memoryDir, "notes.json"))).toBe(true); + }); + + it("should ignore files not matching file_glob before counting/staging", () => { + const h = makeHandlersWithMemory({ max_file_count: 1, file_glob: "*.json" }); + fs.mkdirSync(memoryDir, { recursive: true }); + initGitRepo(memoryDir); + fs.writeFileSync(path.join(memoryDir, "notes.json"), "{}"); + fs.writeFileSync(path.join(memoryDir, "notes.md"), "hello"); + const result = h.pushRepoMemoryHandler({ memory_id: "default" }); + expect(result.isError).toBeUndefined(); + const data = JSON.parse(result.content[0].text); + expect(data.result).toBe("success"); + expect(fs.existsSync(path.join(memoryDir, "notes.md"))).toBe(false); + expect(fs.existsSync(path.join(memoryDir, "notes.json"))).toBe(true); + }); + it("should pass when total folder size is large but staged diff is tiny", () => { const h = makeHandlersWithMemory({ max_patch_size: 50, max_file_size: 1024 * 1024 }); fs.mkdirSync(memoryDir, { recursive: true }); diff --git a/actions/setup/setup.sh b/actions/setup/setup.sh index 3eb617afe74..ba15d66ec28 100755 --- a/actions/setup/setup.sh +++ b/actions/setup/setup.sh @@ -321,6 +321,7 @@ SAFE_OUTPUTS_FILES=( "read_buffer.cjs" "mcp_scripts_validation.cjs" "memory_custom_validation.cjs" + "memory_file_eligibility.cjs" "messages.cjs" "messages_core.cjs" "messages_footer.cjs" diff --git a/pkg/workflow/repo_memory.go b/pkg/workflow/repo_memory.go index 81ebdf3290b..b9b69c93ad0 100644 --- a/pkg/workflow/repo_memory.go +++ b/pkg/workflow/repo_memory.go @@ -362,9 +362,18 @@ func generateRepoMemoryArtifactUpload(builder *strings.Builder, data *WorkflowDa } generateRepoMemorySanitizeFilenamesStep(builder, memory, memoryDir, memoryLabel) - generateRepoMemoryFilterFilesStep(builder, memory, memoryDir, memoryLabel) - validationStepID := generateRepoMemoryCustomValidationStep(builder, memory, memoryDir, memoryLabel) - generateRepoMemoryUploadArtifactStep(builder, memory, memoryDir, memoryLabel, sanitizedID, prefix, validationStepID, pinAction) + filterStepID := generateRepoMemoryFilterFilesStep(builder, memory, memoryDir, memoryLabel) + validationStepID := generateRepoMemoryCustomValidationStep(builder, memory, memoryDir, memoryLabel, filterStepID) + generateRepoMemoryUploadArtifactStep(builder, repoMemoryUploadStepParams{ + memory: memory, + memoryDir: memoryDir, + memoryLabel: memoryLabel, + sanitizedID: sanitizedID, + prefix: prefix, + filterStepID: filterStepID, + validationStepID: validationStepID, + pinAction: pinAction, + }) } } @@ -390,12 +399,16 @@ func generateRepoMemorySanitizeFilenamesStep(builder *strings.Builder, memory Re // and upload. Allowed-extensions and file-glob are persistence filters: ineligible files // are logged and removed here so that custom validation, the uploaded artifact, and the // downstream push all see the same effective file set. -func generateRepoMemoryFilterFilesStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel string) { +// It returns the step's ID (used to gate subsequent validation/upload steps on its +// successful outcome), or "" when no filter is configured for this memory. +func generateRepoMemoryFilterFilesStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel string) string { if len(memory.AllowedExtensions) == 0 && len(memory.FileGlob) == 0 { - return + return "" } + filterStepID := repoMemoryFilterStepID(memory.ID) allowedExtsJSON, _ := json.Marshal(memory.AllowedExtensions) //nolint:jsonmarshalignoredeerror // marshaling a string slice cannot fail fmt.Fprintf(builder, " - name: Filter %s files (%s)\n", memoryLabel, memory.ID) + fmt.Fprintf(builder, " id: %s\n", filterStepID) builder.WriteString(" if: always()\n") fmt.Fprintf(builder, " uses: %s\n", getActionPin("actions/github-script")) builder.WriteString(" env:\n") @@ -407,19 +420,27 @@ func generateRepoMemoryFilterFilesStep(builder *strings.Builder, memory RepoMemo builder.WriteString(" with:\n") builder.WriteString(" script: |\n") builder.WriteString(generateGitHubScriptWithRequire("memory_file_eligibility.cjs")) + return filterStepID } // generateRepoMemoryCustomValidationStep emits the optional custom-validation step and // returns its step ID (used to gate the subsequent upload step), or "" when no custom -// validation is configured for this memory. -func generateRepoMemoryCustomValidationStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel string) string { +// validation is configured for this memory. When filterStepID is non-empty, this step is +// skipped unless the filter step completed successfully, so a filter failure (e.g. an +// fs error while removing ineligible files) can never result in the unfiltered directory +// being validated. +func generateRepoMemoryCustomValidationStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel, filterStepID string) string { if memory.Validation == nil { return "" } validationStepID := repoMemoryValidationStepID(memory.ID) fmt.Fprintf(builder, " - name: Validate %s domain content (%s)\n", memoryLabel, memory.ID) fmt.Fprintf(builder, " id: %s\n", validationStepID) - builder.WriteString(" if: always()\n") + if filterStepID != "" { + fmt.Fprintf(builder, " if: always() && steps.%s.outcome == 'success'\n", filterStepID) + } else { + builder.WriteString(" if: always()\n") + } fmt.Fprintf(builder, " uses: %s\n", getActionPin("actions/github-script")) builder.WriteString(" env:\n") fmt.Fprintf(builder, " MEMORY_DIR: %s\n", memoryDir) @@ -438,23 +459,50 @@ func generateRepoMemoryCustomValidationStep(builder *strings.Builder, memory Rep return validationStepID } +// repoMemoryUploadStepParams bundles the parameters for generateRepoMemoryUploadArtifactStep +// (kept as a struct rather than individual parameters to stay within the repo's +// function-parameter-count lint limit). +type repoMemoryUploadStepParams struct { + memory RepoMemoryEntry + memoryDir string + memoryLabel string + sanitizedID string + prefix string + filterStepID string + validationStepID string + pinAction func(string) string +} + // generateRepoMemoryUploadArtifactStep emits the step that uploads the repo-memory -// directory as an artifact, gated on the custom-validation step's outcome when configured. -func generateRepoMemoryUploadArtifactStep(builder *strings.Builder, memory RepoMemoryEntry, memoryDir, memoryLabel, sanitizedID, prefix, validationStepID string, pinAction func(string) string) { - fmt.Fprintf(builder, " - name: Upload %s artifact (%s)\n", memoryLabel, memory.ID) - if validationStepID != "" { - fmt.Fprintf(builder, " if: always() && steps.%s.outcome == 'success'\n", validationStepID) +// directory as an artifact, gated on the filter and custom-validation steps' outcomes +// when configured, so a filter or validation failure can never result in an unfiltered +// or unvalidated directory being uploaded. +func generateRepoMemoryUploadArtifactStep(builder *strings.Builder, p repoMemoryUploadStepParams) { + fmt.Fprintf(builder, " - name: Upload %s artifact (%s)\n", p.memoryLabel, p.memory.ID) + var conditions []string + if p.filterStepID != "" { + conditions = append(conditions, fmt.Sprintf("steps.%s.outcome == 'success'", p.filterStepID)) + } + if p.validationStepID != "" { + conditions = append(conditions, fmt.Sprintf("steps.%s.outcome == 'success'", p.validationStepID)) + } + if len(conditions) > 0 { + fmt.Fprintf(builder, " if: always() && %s\n", strings.Join(conditions, " && ")) } else { builder.WriteString(" if: always()\n") } - fmt.Fprintf(builder, " uses: %s\n", pinAction("actions/upload-artifact")) + fmt.Fprintf(builder, " uses: %s\n", p.pinAction("actions/upload-artifact")) builder.WriteString(" with:\n") - fmt.Fprintf(builder, " name: %srepo-memory-%s\n", prefix, sanitizedID) - fmt.Fprintf(builder, " path: %s\n", memoryDir) + fmt.Fprintf(builder, " name: %srepo-memory-%s\n", p.prefix, p.sanitizedID) + fmt.Fprintf(builder, " path: %s\n", p.memoryDir) builder.WriteString(" retention-days: 1\n") builder.WriteString(" if-no-files-found: ignore\n") } +func repoMemoryFilterStepID(memoryID string) string { + return memoryValidationStepID("filter_repo_memory", memoryID) +} + func repoMemoryValidationStepID(memoryID string) string { return memoryValidationStepID("validate_repo_memory", memoryID) } diff --git a/pkg/workflow/repo_memory_test.go b/pkg/workflow/repo_memory_test.go index 577bcb8a6fd..21d2506a5fc 100644 --- a/pkg/workflow/repo_memory_test.go +++ b/pkg/workflow/repo_memory_test.go @@ -321,6 +321,78 @@ func TestGenerateRepoMemoryArtifactUpload(t *testing.T) { } } +// TestRepoMemoryFilterStepGatesUpload verifies that when allowed-extensions/file-glob +// filtering is configured, the "Filter ... files" step has an id and the subsequent +// custom-validation and upload-artifact steps require that step's outcome to be +// 'success'. This ensures a filter-step failure (e.g. an fs error while removing +// ineligible files) can never result in an unfiltered directory being validated or +// uploaded — regression for a "fail open" review finding. +func TestRepoMemoryFilterStepGatesUpload(t *testing.T) { + config := &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{ + { + ID: "default", + BranchName: "memory/default", + AllowedExtensions: []string{".json"}, + Validation: &MemoryValidationConfig{ + Script: "// noop", + }, + }, + }, + } + data := &WorkflowData{RepoMemoryConfig: config} + + var builder strings.Builder + generateRepoMemoryArtifactUpload(&builder, data, getActionPin) + output := builder.String() + + filterStepID := repoMemoryFilterStepID("default") + validationStepID := repoMemoryValidationStepID("default") + + assert.Contains(t, output, "id: "+filterStepID, "Filter step must have an id") + + filterPos := strings.Index(output, "id: "+filterStepID) + validationNamePos := strings.Index(output, "Validate repo-memory domain content (default)") + uploadNamePos := strings.Index(output, "Upload repo-memory artifact (default)") + require.Greater(t, filterPos, -1) + require.Greater(t, validationNamePos, -1) + require.Greater(t, uploadNamePos, -1) + assert.Less(t, filterPos, validationNamePos, "Filter step must appear before validation step") + assert.Less(t, validationNamePos, uploadNamePos, "Validation step must appear before upload step") + + validationSection := output[validationNamePos:uploadNamePos] + assert.Contains(t, validationSection, "if: always() && steps."+filterStepID+".outcome == 'success'", + "Validation step must be gated on the filter step's success") + + uploadSection := output[uploadNamePos:] + assert.Contains(t, uploadSection, "steps."+filterStepID+".outcome == 'success'", + "Upload step must be gated on the filter step's success") + assert.Contains(t, uploadSection, "steps."+validationStepID+".outcome == 'success'", + "Upload step must also be gated on the validation step's success") +} + +// TestRepoMemoryNoFilterStepWhenNoFilterConfigured verifies that when no +// allowed-extensions/file-glob is configured, no filter step id is emitted and the +// upload step is not gated on a nonexistent filter step. +func TestRepoMemoryNoFilterStepWhenNoFilterConfigured(t *testing.T) { + config := &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{ + { + ID: "default", + BranchName: "memory/default", + }, + }, + } + data := &WorkflowData{RepoMemoryConfig: config} + + var builder strings.Builder + generateRepoMemoryArtifactUpload(&builder, data, getActionPin) + output := builder.String() + + assert.NotContains(t, output, "Filter repo-memory files (default)", "No filter step should be emitted") + assert.NotContains(t, output, repoMemoryFilterStepID("default"), "No filter step id should be referenced") +} + // TestRepoMemoryPromptGeneration tests that prompt section is generated correctly func TestRepoMemoryPromptGeneration(t *testing.T) { config := &RepoMemoryConfig{ diff --git a/pkg/workflow/safe_outputs_config_generation.go b/pkg/workflow/safe_outputs_config_generation.go index e6895691756..cda7a8694b0 100644 --- a/pkg/workflow/safe_outputs_config_generation.go +++ b/pkg/workflow/safe_outputs_config_generation.go @@ -37,205 +37,238 @@ func generateSafeOutputsConfig(data *WorkflowData) (string, error) { safeOutputsConfigLog.Print("Generating safe outputs configuration for workflow") safeOutputsConfig := make(map[string]any) - engineManifestFiles, engineManifestPathPrefixes := getEngineAgentFileInfoFromWorkflowData(data) - - // Standard handler configs — sourced from handlerRegistry (same as GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG) - for handlerName, builder := range handlerRegistry { - if handlerCfg := builder(data.SafeOutputs); handlerCfg != nil { - injectCurrentCheckoutPatchWorkspacePath(handlerName, handlerCfg, data) - injectCheckoutMapping(handlerName, handlerCfg, data) - excludeFiles := ParseStringArrayFromConfig(handlerCfg, "_protected_files_exclude", nil) - // Strip the internal sentinel key used by the handler manager for compile-time - // exclusion processing — it must not be forwarded to the runtime config.json. - delete(handlerCfg, "_protected_files_exclude") - if _, hasProtectedFiles := handlerCfg["protected_files"]; hasProtectedFiles { - fullManifestFiles := getAllManifestFiles(engineManifestFiles...) - fullPathPrefixes := getProtectedPathPrefixes(engineManifestPathPrefixes...) - handlerCfg["protected_files"] = sliceutil.Exclude(fullManifestFiles, excludeFiles...) - filteredPrefixes := sliceutil.Exclude(fullPathPrefixes, excludeFiles...) - if len(filteredPrefixes) > 0 { - handlerCfg["protected_path_prefixes"] = filteredPrefixes - } else { - delete(handlerCfg, "protected_path_prefixes") - } - // Compute which top-level dot-folder prefixes are excluded so the runtime - // dot-folder check can skip them. - if dotFolderExcludes := getDotFolderExcludes(excludeFiles); len(dotFolderExcludes) > 0 { - handlerCfg["protected_dot_folder_excludes"] = dotFolderExcludes - } - } - if data.SafeOutputs != nil && data.SafeOutputs.DataEnabled && isDataSchemaEnabledType(handlerName) { - handlerCfg["data_enabled"] = true - if data.SafeOutputs.NormalizedDataSchema != nil { - handlerCfg["data_schema"] = data.SafeOutputs.NormalizedDataSchema - } else if strings.TrimSpace(data.SafeOutputs.DataSchemaExpression) != "" { - handlerCfg["data_schema"] = data.SafeOutputs.DataSchemaExpression - } - } - safeOutputsConfig[handlerName] = handlerCfg - } - } + addStandardHandlerConfigs(safeOutputsConfig, data) if handlerConfig := buildCommentMemoryHandlerConfig(data.CommentMemoryConfig, data.SafeOutputs.Footer); handlerConfig != nil { safeOutputsConfig[commentMemoryHandlerKey] = handlerConfig } - // Safe-jobs configuration: custom output types that run as separate GitHub Actions jobs. - // These are not standard handlers but must be in config.json so the ingestion step can - // validate and route those output types. - if len(data.SafeOutputs.Jobs) > 0 { - safeOutputsConfigLog.Printf("Processing %d safe job configurations", len(data.SafeOutputs.Jobs)) - for jobName, jobConfig := range data.SafeOutputs.Jobs { - safeOutputsConfigLog.Printf("Generating config for safe job: %s", jobName) - safeJobConfig := map[string]any{} - if jobConfig.Description != "" { - safeJobConfig["description"] = jobConfig.Description - } - if jobConfig.Output != "" { - safeJobConfig["output"] = jobConfig.Output - } - if jobConfig.Max > 0 { - safeJobConfig["max"] = jobConfig.Max - } - if len(jobConfig.Inputs) > 0 { - inputsConfig := make(map[string]any) - for inputName, inputDef := range jobConfig.Inputs { - inputConfig := map[string]any{ - "type": inputDef.Type, - "description": inputDef.Description, - "required": inputDef.Required, - } - if inputDef.Default != "" { - inputConfig["default"] = inputDef.Default - } - if len(inputDef.Options) > 0 { - inputConfig["options"] = inputDef.Options - } - inputsConfig[inputName] = inputConfig - } - safeJobConfig["inputs"] = inputsConfig - } - safeOutputsConfig[jobName] = safeJobConfig - } + addSafeJobsConfig(safeOutputsConfig, data.SafeOutputs.Jobs) + addSafeScriptsConfig(safeOutputsConfig, data.SafeOutputs.Scripts) + if err := addSafeActionsConfig(safeOutputsConfig, data.SafeOutputs.Actions); err != nil { + return "", err } + addMentionsConfig(safeOutputsConfig, data.SafeOutputs) + addPushRepoMemoryConfig(safeOutputsConfig, data.RepoMemoryConfig) - // Safe-scripts configuration: script output types handled inline by the handler manager. - if len(data.SafeOutputs.Scripts) > 0 { - safeOutputsConfigLog.Printf("Processing %d safe script configurations", len(data.SafeOutputs.Scripts)) - for scriptName, scriptConfig := range data.SafeOutputs.Scripts { - normalizedName := stringutil.NormalizeSafeOutputIdentifier(scriptName) - safeOutputsConfigLog.Printf("Generating config for safe script: %s (normalized: %s)", scriptName, normalizedName) - safeScriptConfigMap := map[string]any{} - if scriptConfig.Description != "" { - safeScriptConfigMap["description"] = scriptConfig.Description + if len(safeOutputsConfig) == 0 { + return "", nil + } + // The agent job never depends on the custom jobs listed in safe-outputs.needs (those + // dependencies are only wired onto the later safe_outputs handler job by + // buildSafeOutputsJobNeeds). Any templated expression referencing needs. for one of + // those jobs would therefore be unresolvable inside the agent job and trip an actionlint + // "undefined property" error. Neutralize such expressions here; the handler job's own copy + // of the config (GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG) is unaffected and keeps the real value. + sanitizeAgentSafeOutputsConfig(safeOutputsConfig, data.SafeOutputs.Needs) + configJSON, err := marshalSafeOutputsConfig(safeOutputsConfig) + if err != nil { + return "", fmt.Errorf("marshaling safe-outputs config: %w", err) + } + safeOutputsConfigLog.Printf("Safe outputs config generation complete: %d tool types configured", len(safeOutputsConfig)) + return string(configJSON), nil +} + +// addStandardHandlerConfigs adds config for every registered standard safe-output +// handler (same source as GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG) to safeOutputsConfig. +func addStandardHandlerConfigs(safeOutputsConfig map[string]any, data *WorkflowData) { + engineManifestFiles, engineManifestPathPrefixes := getEngineAgentFileInfoFromWorkflowData(data) + + for handlerName, builder := range handlerRegistry { + handlerCfg := builder(data.SafeOutputs) + if handlerCfg == nil { + continue + } + injectCurrentCheckoutPatchWorkspacePath(handlerName, handlerCfg, data) + injectCheckoutMapping(handlerName, handlerCfg, data) + excludeFiles := ParseStringArrayFromConfig(handlerCfg, "_protected_files_exclude", nil) + // Strip the internal sentinel key used by the handler manager for compile-time + // exclusion processing — it must not be forwarded to the runtime config.json. + delete(handlerCfg, "_protected_files_exclude") + if _, hasProtectedFiles := handlerCfg["protected_files"]; hasProtectedFiles { + fullManifestFiles := getAllManifestFiles(engineManifestFiles...) + fullPathPrefixes := getProtectedPathPrefixes(engineManifestPathPrefixes...) + handlerCfg["protected_files"] = sliceutil.Exclude(fullManifestFiles, excludeFiles...) + filteredPrefixes := sliceutil.Exclude(fullPathPrefixes, excludeFiles...) + if len(filteredPrefixes) > 0 { + handlerCfg["protected_path_prefixes"] = filteredPrefixes + } else { + delete(handlerCfg, "protected_path_prefixes") } - if len(scriptConfig.Inputs) > 0 { - inputsConfig := make(map[string]any) - for inputName, inputDef := range scriptConfig.Inputs { - inputConfig := map[string]any{ - "type": inputDef.Type, - "description": inputDef.Description, - "required": inputDef.Required, - } - if inputDef.Default != "" { - inputConfig["default"] = inputDef.Default - } - if len(inputDef.Options) > 0 { - inputConfig["options"] = inputDef.Options - } - inputsConfig[inputName] = inputConfig - } - safeScriptConfigMap["inputs"] = inputsConfig + // Compute which top-level dot-folder prefixes are excluded so the runtime + // dot-folder check can skip them. + if dotFolderExcludes := getDotFolderExcludes(excludeFiles); len(dotFolderExcludes) > 0 { + handlerCfg["protected_dot_folder_excludes"] = dotFolderExcludes } - safeOutputsConfig[normalizedName] = safeScriptConfigMap } - } - - // Safe-actions configuration: custom GitHub Actions exposed as safe output tools. - // The normalized action names are added as config keys so both MCP server implementations - // recognise them as enabled tools (the tool schema is already in tools.json via - // tools_meta.json; the MCP server just needs to see the name in config.json). - if len(data.SafeOutputs.Actions) > 0 { - safeOutputsConfigLog.Printf("Processing %d safe action configurations", len(data.SafeOutputs.Actions)) - for actionName := range data.SafeOutputs.Actions { - normalizedName := stringutil.NormalizeSafeOutputIdentifier(actionName) - if _, exists := safeOutputsConfig[normalizedName]; exists { - return "", fmt.Errorf( - "safe-outputs action %q has a normalized name %q that conflicts with an existing safe outputs config entry; rename the action to avoid the conflict", - actionName, - normalizedName, - ) + if data.SafeOutputs != nil && data.SafeOutputs.DataEnabled && isDataSchemaEnabledType(handlerName) { + handlerCfg["data_enabled"] = true + if data.SafeOutputs.NormalizedDataSchema != nil { + handlerCfg["data_schema"] = data.SafeOutputs.NormalizedDataSchema + } else if strings.TrimSpace(data.SafeOutputs.DataSchemaExpression) != "" { + handlerCfg["data_schema"] = data.SafeOutputs.DataSchemaExpression } - safeOutputsConfigLog.Printf("Adding safe action to config: %s (normalized: %s)", actionName, normalizedName) - safeOutputsConfig[normalizedName] = true } + safeOutputsConfig[handlerName] = handlerCfg } +} +// addMentionsConfig adds mentions and max-bot-mentions configuration (consumed by the +// ingestion step, not by standard handlers) to safeOutputsConfig. +func addMentionsConfig(safeOutputsConfig map[string]any, safeOutputs *SafeOutputsConfig) { // Mentions configuration: controls which @mentions are allowed in AI output. - // This is consumed by the ingestion step, not by standard handlers. - if data.SafeOutputs.Mentions != nil { - mentionsConfig := buildMentionsHandlerConfig(data.SafeOutputs.Mentions) + if safeOutputs.Mentions != nil { + mentionsConfig := buildMentionsHandlerConfig(safeOutputs.Mentions) if len(mentionsConfig) > 0 { safeOutputsConfig["mentions"] = mentionsConfig } } // Max bot mentions: limits bot trigger references (e.g. "fixes #123") in AI output. - // Consumed by the ingestion step as a global config knob. // Store as integer when possible (matching original behavior), or as expression string. - if data.SafeOutputs.MaxBotMentions != nil { - v := *data.SafeOutputs.MaxBotMentions - if n := templatableIntValue(data.SafeOutputs.MaxBotMentions); n > 0 { + if safeOutputs.MaxBotMentions != nil { + v := *safeOutputs.MaxBotMentions + if n := templatableIntValue(safeOutputs.MaxBotMentions); n > 0 { safeOutputsConfig["max_bot_mentions"] = n } else if strings.HasPrefix(v, "${{") { safeOutputsConfig["max_bot_mentions"] = v } } +} - // Push-repo-memory configuration: enables the push_repo_memory MCP tool for early - // size validation during the agent session. - if data.RepoMemoryConfig != nil && len(data.RepoMemoryConfig.Memories) > 0 { - var memories []map[string]any - for _, memory := range data.RepoMemoryConfig.Memories { - memoryConfig := map[string]any{ - "id": memory.ID, - "dir": constants.TmpRepoMemoryDir + memory.ID, - "max_file_size": memory.MaxFileSize, - "max_patch_size": memory.MaxPatchSize, - "max_file_count": memory.MaxFileCount, - } - if memory.FormatJSON { - memoryConfig["format_json"] = true - } - if memory.Validation != nil { - memoryConfig["validation"] = map[string]any{ - "script": memory.Validation.Script, - "timeout": memoryValidationTimeoutSeconds(memory.Validation), - } - } - memories = append(memories, memoryConfig) +// addSafeJobsConfig adds safe-jobs configuration (custom output types that run as +// separate GitHub Actions jobs) to safeOutputsConfig. These are not standard handlers +// but must be in config.json so the ingestion step can validate and route those output types. +func addSafeJobsConfig(safeOutputsConfig map[string]any, jobs map[string]*SafeJobConfig) { + if len(jobs) == 0 { + return + } + safeOutputsConfigLog.Printf("Processing %d safe job configurations", len(jobs)) + for jobName, jobConfig := range jobs { + safeOutputsConfigLog.Printf("Generating config for safe job: %s", jobName) + safeJobConfig := map[string]any{} + if jobConfig.Description != "" { + safeJobConfig["description"] = jobConfig.Description + } + if jobConfig.Output != "" { + safeJobConfig["output"] = jobConfig.Output } - safeOutputsConfig["push_repo_memory"] = map[string]any{ - "memories": memories, + if jobConfig.Max > 0 { + safeJobConfig["max"] = jobConfig.Max } - safeOutputsConfigLog.Printf("Added push_repo_memory config with %d memory entries", len(data.RepoMemoryConfig.Memories)) + if inputsConfig := buildSafeOutputInputsConfig(jobConfig.Inputs); inputsConfig != nil { + safeJobConfig["inputs"] = inputsConfig + } + safeOutputsConfig[jobName] = safeJobConfig } +} - if len(safeOutputsConfig) == 0 { - return "", nil +// addSafeScriptsConfig adds safe-scripts configuration (script output types handled +// inline by the handler manager) to safeOutputsConfig. +func addSafeScriptsConfig(safeOutputsConfig map[string]any, scripts map[string]*SafeScriptConfig) { + if len(scripts) == 0 { + return } - // The agent job never depends on the custom jobs listed in safe-outputs.needs (those - // dependencies are only wired onto the later safe_outputs handler job by - // buildSafeOutputsJobNeeds). Any templated expression referencing needs. for one of - // those jobs would therefore be unresolvable inside the agent job and trip an actionlint - // "undefined property" error. Neutralize such expressions here; the handler job's own copy - // of the config (GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG) is unaffected and keeps the real value. - sanitizeAgentSafeOutputsConfig(safeOutputsConfig, data.SafeOutputs.Needs) - configJSON, err := marshalSafeOutputsConfig(safeOutputsConfig) - if err != nil { - return "", fmt.Errorf("marshaling safe-outputs config: %w", err) + safeOutputsConfigLog.Printf("Processing %d safe script configurations", len(scripts)) + for scriptName, scriptConfig := range scripts { + normalizedName := stringutil.NormalizeSafeOutputIdentifier(scriptName) + safeOutputsConfigLog.Printf("Generating config for safe script: %s (normalized: %s)", scriptName, normalizedName) + safeScriptConfigMap := map[string]any{} + if scriptConfig.Description != "" { + safeScriptConfigMap["description"] = scriptConfig.Description + } + if inputsConfig := buildSafeOutputInputsConfig(scriptConfig.Inputs); inputsConfig != nil { + safeScriptConfigMap["inputs"] = inputsConfig + } + safeOutputsConfig[normalizedName] = safeScriptConfigMap } - safeOutputsConfigLog.Printf("Safe outputs config generation complete: %d tool types configured", len(safeOutputsConfig)) - return string(configJSON), nil +} + +// buildSafeOutputInputsConfig converts safe-job/safe-script input definitions into +// the map[string]any shape expected in config.json, or nil when there are none. +func buildSafeOutputInputsConfig(inputs map[string]*InputDefinition) map[string]any { + if len(inputs) == 0 { + return nil + } + inputsConfig := make(map[string]any) + for inputName, inputDef := range inputs { + inputConfig := map[string]any{ + "type": inputDef.Type, + "description": inputDef.Description, + "required": inputDef.Required, + } + if inputDef.Default != "" { + inputConfig["default"] = inputDef.Default + } + if len(inputDef.Options) > 0 { + inputConfig["options"] = inputDef.Options + } + inputsConfig[inputName] = inputConfig + } + return inputsConfig +} + +// addSafeActionsConfig adds safe-actions configuration (custom GitHub Actions exposed +// as safe output tools) to safeOutputsConfig. The normalized action names are added as +// config keys so both MCP server implementations recognise them as enabled tools (the +// tool schema is already in tools.json via tools_meta.json; the MCP server just needs +// to see the name in config.json). +func addSafeActionsConfig(safeOutputsConfig map[string]any, actions map[string]*SafeOutputActionConfig) error { + if len(actions) == 0 { + return nil + } + safeOutputsConfigLog.Printf("Processing %d safe action configurations", len(actions)) + for actionName := range actions { + normalizedName := stringutil.NormalizeSafeOutputIdentifier(actionName) + if _, exists := safeOutputsConfig[normalizedName]; exists { + return fmt.Errorf( + "safe-outputs action %q has a normalized name %q that conflicts with an existing safe outputs config entry; rename the action to avoid the conflict", + actionName, + normalizedName, + ) + } + safeOutputsConfigLog.Printf("Adding safe action to config: %s (normalized: %s)", actionName, normalizedName) + safeOutputsConfig[normalizedName] = true + } + return nil +} + +// addPushRepoMemoryConfig adds push_repo_memory configuration (enables the +// push_repo_memory MCP tool for early size/eligibility validation during the agent +// session) to safeOutputsConfig, when repo-memory is configured. +func addPushRepoMemoryConfig(safeOutputsConfig map[string]any, repoMemoryConfig *RepoMemoryConfig) { + if repoMemoryConfig == nil || len(repoMemoryConfig.Memories) == 0 { + return + } + var memories []map[string]any + for _, memory := range repoMemoryConfig.Memories { + memoryConfig := map[string]any{ + "id": memory.ID, + "dir": constants.TmpRepoMemoryDir + memory.ID, + "max_file_size": memory.MaxFileSize, + "max_patch_size": memory.MaxPatchSize, + "max_file_count": memory.MaxFileCount, + } + if len(memory.AllowedExtensions) > 0 { + memoryConfig["allowed_extensions"] = memory.AllowedExtensions + } + if len(memory.FileGlob) > 0 { + memoryConfig["file_glob"] = strings.Join(memory.FileGlob, " ") + } + if memory.FormatJSON { + memoryConfig["format_json"] = true + } + if memory.Validation != nil { + memoryConfig["validation"] = map[string]any{ + "script": memory.Validation.Script, + "timeout": memoryValidationTimeoutSeconds(memory.Validation), + } + } + memories = append(memories, memoryConfig) + } + safeOutputsConfig["push_repo_memory"] = map[string]any{ + "memories": memories, + } + safeOutputsConfigLog.Printf("Added push_repo_memory config with %d memory entries", len(repoMemoryConfig.Memories)) } func getEngineAgentFileInfoFromWorkflowData(data *WorkflowData) (manifestFiles []string, pathPrefixes []string) { @@ -261,30 +294,11 @@ func getEngineAgentFileInfoFromWorkflowData(data *WorkflowData) (manifestFiles [ return provider.GetAgentManifestFiles(), provider.GetAgentManifestPathPrefixes() } -// generateCustomJobToolDefinition creates an MCP tool definition for a custom safe-output job. -// Returns a map representing the tool definition in MCP format with name, description, and inputSchema. -func generateCustomJobToolDefinition(jobName string, jobConfig *SafeJobConfig) map[string]any { - safeOutputsConfigLog.Printf("Generating tool definition for custom job: %s", jobName) - - description := jobConfig.Description - if description == "" { - description = fmt.Sprintf("Execute the %s custom job", jobName) - } - - inputSchema := map[string]any{ - "type": "object", - "properties": make(map[string]any), - "additionalProperties": false, - } - +// populateCustomJobToolProperties fills properties (an MCP tool inputSchema.properties +// map) from the given safe-job input definitions, returning the names of required inputs. +func populateCustomJobToolProperties(properties map[string]any, inputs map[string]*InputDefinition) []string { var requiredFields []string - properties, ok := inputSchema["properties"].(map[string]any) - if !ok { - properties = make(map[string]any) - inputSchema["properties"] = properties - } - - for inputName, inputDef := range jobConfig.Inputs { + for inputName, inputDef := range inputs { property := map[string]any{} if inputDef.Description != "" { @@ -315,6 +329,31 @@ func generateCustomJobToolDefinition(jobName string, jobConfig *SafeJobConfig) m properties[inputName] = property } + return requiredFields +} + +// generateCustomJobToolDefinition creates an MCP tool definition for a custom safe-output job. +// Returns a map representing the tool definition in MCP format with name, description, and inputSchema. +func generateCustomJobToolDefinition(jobName string, jobConfig *SafeJobConfig) map[string]any { + safeOutputsConfigLog.Printf("Generating tool definition for custom job: %s", jobName) + + description := jobConfig.Description + if description == "" { + description = fmt.Sprintf("Execute the %s custom job", jobName) + } + + inputSchema := map[string]any{ + "type": "object", + "properties": make(map[string]any), + "additionalProperties": false, + } + + properties, ok := inputSchema["properties"].(map[string]any) + if !ok { + properties = make(map[string]any) + inputSchema["properties"] = properties + } + requiredFields := populateCustomJobToolProperties(properties, jobConfig.Inputs) if len(requiredFields) > 0 { sort.Strings(requiredFields) diff --git a/pkg/workflow/safe_outputs_config_generation_test.go b/pkg/workflow/safe_outputs_config_generation_test.go index 1d4fa7c3ae2..649dd01db8a 100644 --- a/pkg/workflow/safe_outputs_config_generation_test.go +++ b/pkg/workflow/safe_outputs_config_generation_test.go @@ -1124,6 +1124,60 @@ func TestGenerateSafeOutputsConfigRepoMemory(t *testing.T) { assert.InDelta(t, float64(2048), mem1["max_file_size"], 0.0001, "Second memory max_file_size should match") } +// TestGenerateSafeOutputsConfigRepoMemoryFilters tests that generateSafeOutputsConfig +// passes allowed-extensions/file-glob through to the push_repo_memory preflight config, +// so the MCP tool preflight can apply the same eligibility filtering as the agent-side +// filter step and the push job (regression: preflight was previously unfiltered). +func TestGenerateSafeOutputsConfigRepoMemoryFilters(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{}, + RepoMemoryConfig: &RepoMemoryConfig{ + Memories: []RepoMemoryEntry{ + { + ID: "default", + MaxFileSize: 5120, + MaxPatchSize: 20480, + MaxFileCount: 50, + AllowedExtensions: []string{".json", ".md"}, + FileGlob: []string{"*.json", "metrics/**"}, + }, + { + ID: "notes", + MaxFileSize: 2048, + MaxPatchSize: 8192, + MaxFileCount: 20, + }, + }, + }, + } + + result, err := generateSafeOutputsConfig(data) + require.NoError(t, err, "generateSafeOutputsConfig should not return an error") + + var parsed map[string]any + require.NoError(t, json.Unmarshal([]byte(result), &parsed), "Result must be valid JSON") + + pushRepoMemory, ok := parsed["push_repo_memory"].(map[string]any) + require.True(t, ok, "Expected push_repo_memory key in config") + memories, ok := pushRepoMemory["memories"].([]any) + require.True(t, ok, "Expected memories to be an array") + require.Len(t, memories, 2, "Expected 2 memory entries") + + mem0, ok := memories[0].(map[string]any) + require.True(t, ok, "First memory entry should be a map") + allowedExts, ok := mem0["allowed_extensions"].([]any) + require.True(t, ok, "First memory should carry allowed_extensions") + assert.Equal(t, []any{".json", ".md"}, allowedExts, "allowed_extensions should be passed through") + assert.Equal(t, "*.json metrics/**", mem0["file_glob"], "file_glob should be joined with spaces") + + mem1, ok := memories[1].(map[string]any) + require.True(t, ok, "Second memory entry should be a map") + _, hasAllowedExts := mem1["allowed_extensions"] + assert.False(t, hasAllowedExts, "Second memory should not carry allowed_extensions when unset") + _, hasFileGlob := mem1["file_glob"] + assert.False(t, hasFileGlob, "Second memory should not carry file_glob when unset") +} + // TestGenerateSafeOutputsConfigNoRepoMemory tests that push_repo_memory is absent // from the config when RepoMemoryConfig is not present. func TestGenerateSafeOutputsConfigNoRepoMemory(t *testing.T) { From 91e55b227099925ff3078606f247688e60e7c7d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 06:30:03 +0000 Subject: [PATCH 6/7] repo-memory: add real behavioral test coverage for push-job eligibility scan Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- actions/setup/js/push_repo_memory.test.cjs | 68 ++++++++++++++++++++++ pkg/workflow/repo_memory_test.go | 32 +++++++++- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/actions/setup/js/push_repo_memory.test.cjs b/actions/setup/js/push_repo_memory.test.cjs index b96e865b1e0..7114cc8828a 100644 --- a/actions/setup/js/push_repo_memory.test.cjs +++ b/actions/setup/js/push_repo_memory.test.cjs @@ -1654,6 +1654,74 @@ describe("push_repo_memory.cjs - allowed-extensions persistence filter (regressi expect(filterIdx).toBeLessThan(formatIdx); expect(filterIdx).toBeLessThan(validateIdx); }); + + describe("behavioral: scanDirectory's inline eligibility check against real fixtures", () => { + // push_repo_memory.cjs cannot be driven end-to-end through main() in this test + // environment: it needs a successful `git fetch`/checkout before scanDirectory + // runs, and vi.doMock cannot intercept CJS require() calls for git_helpers.cjs + // here (see "should propagate git fetch authentication failure..." above, which + // documents the same limitation). So this test drives the *actual* eligibility + // helpers push_repo_memory.cjs's scanDirectory calls inline (isMemoryFileEligible, + // compileFileGlobPatterns, both imported for real, not mocked) against a real + // artifact directory on disk, mirroring the exact scan loop in the script. + const nodeFs = require("fs"); + const nodePath = require("path"); + const os = require("os"); + + let artifactDir; + + beforeEach(() => { + artifactDir = nodeFs.mkdtempSync(nodePath.join(os.tmpdir(), "push-repo-memory-scan-")); + }); + + afterEach(() => { + nodeFs.rmSync(artifactDir, { recursive: true, force: true }); + }); + + /** Mirrors the scanDirectory() walk in push_repo_memory.cjs, real fs + real eligibility helpers. */ + async function scanEligibleFiles(dirPath, allowedExtensions, fileGlobFilter) { + const { compileFileGlobPatterns, isMemoryFileEligible } = await import("./memory_file_eligibility.cjs"); + const { compiledPatterns } = compileFileGlobPatterns(fileGlobFilter); + const kept = []; + const filteredOut = []; + (function walk(dir, relativePath = "") { + for (const entry of nodeFs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = nodePath.join(dir, entry.name); + const relativeFilePath = relativePath ? nodePath.join(relativePath, entry.name) : entry.name; + if (entry.isDirectory()) { + walk(fullPath, relativeFilePath); + } else if (entry.isFile()) { + const eligibility = isMemoryFileEligible(relativeFilePath, allowedExtensions, compiledPatterns); + if (eligibility.eligible) { + kept.push(relativeFilePath.replace(/\\/g, "/")); + } else { + filteredOut.push(relativeFilePath.replace(/\\/g, "/")); + } + } + } + })(dirPath); + return { kept, filteredOut }; + } + + it("filters out a disallowed-extension file and keeps the eligible one (notes.json.new regression)", async () => { + nodeFs.writeFileSync(nodePath.join(artifactDir, "notes.json"), "{}"); + nodeFs.writeFileSync(nodePath.join(artifactDir, "notes.json.new"), "{}"); + + const { kept, filteredOut } = await scanEligibleFiles(artifactDir, [".json"], ""); + + expect(kept).toEqual(["notes.json"]); + expect(filteredOut).toEqual(["notes.json.new"]); + }); + + it("filters out every file when none are eligible, leaving nothing to copy", async () => { + nodeFs.writeFileSync(nodePath.join(artifactDir, "notes.json.new"), "{}"); + + const { kept, filteredOut } = await scanEligibleFiles(artifactDir, [".json"], ""); + + expect(kept).toEqual([]); + expect(filteredOut).toEqual(["notes.json.new"]); + }); + }); }); // ────────────────────────────────────────────────────────────────────────────── diff --git a/pkg/workflow/repo_memory_test.go b/pkg/workflow/repo_memory_test.go index 21d2506a5fc..f9d3e0d678f 100644 --- a/pkg/workflow/repo_memory_test.go +++ b/pkg/workflow/repo_memory_test.go @@ -334,6 +334,7 @@ func TestRepoMemoryFilterStepGatesUpload(t *testing.T) { ID: "default", BranchName: "memory/default", AllowedExtensions: []string{".json"}, + FileGlob: []string{"*.json", "notes/*.md"}, Validation: &MemoryValidationConfig{ Script: "// noop", }, @@ -351,15 +352,25 @@ func TestRepoMemoryFilterStepGatesUpload(t *testing.T) { assert.Contains(t, output, "id: "+filterStepID, "Filter step must have an id") - filterPos := strings.Index(output, "id: "+filterStepID) + sanitizeNamePos := strings.Index(output, "Sanitize repo-memory filenames (default)") + filterNamePos := strings.Index(output, "Filter repo-memory files (default)") + filterIDPos := strings.Index(output, "id: "+filterStepID) validationNamePos := strings.Index(output, "Validate repo-memory domain content (default)") uploadNamePos := strings.Index(output, "Upload repo-memory artifact (default)") - require.Greater(t, filterPos, -1) + require.Greater(t, sanitizeNamePos, -1) + require.Greater(t, filterNamePos, -1) require.Greater(t, validationNamePos, -1) require.Greater(t, uploadNamePos, -1) - assert.Less(t, filterPos, validationNamePos, "Filter step must appear before validation step") + assert.Less(t, sanitizeNamePos, filterNamePos, "Sanitize step must appear before filter step") + assert.Less(t, filterNamePos, validationNamePos, "Filter step must appear before validation step") assert.Less(t, validationNamePos, uploadNamePos, "Validation step must appear before upload step") + filterSection := output[filterNamePos:validationNamePos] + assert.Contains(t, filterSection, "id: "+filterStepID, "Filter step id must appear within the filter step") + assert.Contains(t, filterSection, `ALLOWED_EXTENSIONS: '[".json"]'`, "Filter step must set ALLOWED_EXTENSIONS to the JSON-encoded extensions list") + assert.Contains(t, filterSection, `FILE_GLOB_FILTER: "*.json notes/*.md"`, "Filter step must set FILE_GLOB_FILTER to the space-joined glob patterns") + assert.Greater(t, filterIDPos, filterNamePos, "Filter step id must appear after its name") + validationSection := output[validationNamePos:uploadNamePos] assert.Contains(t, validationSection, "if: always() && steps."+filterStepID+".outcome == 'success'", "Validation step must be gated on the filter step's success") @@ -371,6 +382,21 @@ func TestRepoMemoryFilterStepGatesUpload(t *testing.T) { "Upload step must also be gated on the validation step's success") } +// TestRepoMemoryFilterStepEmptyBothFieldsSkipsFilter verifies that the "no filter step" +// branch is only reachable when a caller explicitly leaves both AllowedExtensions and +// FileGlob empty — i.e. it's not accidentally triggered by a nil vs. empty-slice distinction. +func TestRepoMemoryFilterStepEmptyBothFieldsSkipsFilter(t *testing.T) { + var builder strings.Builder + stepID := generateRepoMemoryFilterFilesStep(&builder, RepoMemoryEntry{ + ID: "default", + AllowedExtensions: []string{}, + FileGlob: []string{}, + }, "/tmp/gh-aw/repo-memory/default", "repo-memory") + + assert.Empty(t, stepID, "No filter step id should be returned when both fields are empty slices") + assert.Empty(t, builder.String(), "No filter step should be emitted when both fields are empty slices") +} + // TestRepoMemoryNoFilterStepWhenNoFilterConfigured verifies that when no // allowed-extensions/file-glob is configured, no filter step id is emitted and the // upload step is not gated on a nonexistent filter step. From e52731a208b217ad3b85b7b2cbd595c59b7e6682 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 08:49:23 +0000 Subject: [PATCH 7/7] docs: fix stale slashless file-glob depth semantics after matchSubfolderRoot removal Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- docs/src/content/docs/reference/repo-memory.md | 6 +++--- pkg/workflow/repo_memory_validation.go | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/src/content/docs/reference/repo-memory.md b/docs/src/content/docs/reference/repo-memory.md index 6191ea36f3c..44b2869e738 100644 --- a/docs/src/content/docs/reference/repo-memory.md +++ b/docs/src/content/docs/reference/repo-memory.md @@ -48,11 +48,11 @@ tools: **File Glob Matching Rules**: - Patterns are matched against the **relative path** within the artifact directory — do **not** include the branch name. -- **Slashless patterns** (no `/` in the pattern, e.g. `*.json`, `*.md`) match files at the root of a single memory subfolder — **depth 1 only**. They do _not_ match files at the artifact root (depth 0) or deeper than one subfolder level (depth 2+). +- **Slashless patterns** (no `/` in the pattern, e.g. `*.json`, `*.md`) are matched against the full relative path, so a single `*` only matches files at the **artifact root (depth 0)**. They do _not_ match files inside subfolders (depth 1+) — use a pattern containing `/` (e.g. `**/*.json`) for that. - **Patterns containing `/`** (e.g. `metrics/**`, `data/*.csv`) are matched against the full relative path from the artifact root and work as standard glob expressions. - **Absolute paths** (patterns starting with `/`) are **not supported** and are rejected at compile time and runtime. -Example: with the default filter `["*.json", "*.md"]`, the file `discussion-task-miner/processed-discussions.json` is persisted (depth 1 ✓), but `processed-discussions.json` (depth 0) and `discussion-task-miner/archive/old.json` (depth 2) are not. +Example: with the default filter `["*.json", "*.md"]`, the root-level file `processed-discussions.json` is persisted (depth 0 ✓), but `discussion-task-miner/processed-discussions.json` (depth 1) is not — use `["**/*.json", "**/*.md"]` to also match files nested in subfolders. ## Multiple Configurations @@ -69,7 +69,7 @@ tools: --- ``` -Mounts at `/tmp/gh-aw/repo-memory-{id}/` during workflow execution. The required `id` determines the folder name, and `branch-name` defaults to `{branch-prefix}/{id}` with `memory` as the default prefix. Files are stored inside the branch under that branch-name path. File globs always match the relative path within the artifact directory, so never include the branch name; slashless patterns such as `*.json` match only the root of a memory subfolder (depth 1). +Mounts at `/tmp/gh-aw/repo-memory-{id}/` during workflow execution. The required `id` determines the folder name, and `branch-name` defaults to `{branch-prefix}/{id}` with `memory` as the default prefix. Files are stored inside the branch under that branch-name path. File globs always match the relative path within the artifact directory, so never include the branch name; slashless patterns such as `*.json` match only files at the artifact root (depth 0). ## Behavior diff --git a/pkg/workflow/repo_memory_validation.go b/pkg/workflow/repo_memory_validation.go index 47cea565f63..6defe5c88ae 100644 --- a/pkg/workflow/repo_memory_validation.go +++ b/pkg/workflow/repo_memory_validation.go @@ -71,8 +71,9 @@ func validateNoDuplicateMemoryIDs(memories []RepoMemoryEntry) error { // validateFileGlobPatterns validates file-glob patterns for a repo-memory entry. // -// Patterns are evaluated relative to the memory subfolder root (depth 1 from the artifact root). -// Slashless patterns such as "*.json" match files at the root of any single memory subfolder. +// Patterns are evaluated against the full relative path from the artifact root. +// Slashless patterns such as "*.json" match only files at the artifact root (depth 0), +// since a single "*" does not cross "/"; use "**/*.json" to also match nested files. // Patterns containing "/" match against the full relative path from the artifact root. // // Rejected patterns: