diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 0eeb999675e..4aefa273492 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -165,6 +165,11 @@ } }, "containers": { + "ghcr.io/fabio-rovai/open-ontologies:latest": { + "image": "ghcr.io/fabio-rovai/open-ontologies:latest", + "digest": "sha256:2932c10682eac29ccf840a6bd6c4c7c82c5ce770ad9e94697d44057187452530", + "pinned_image": "ghcr.io/fabio-rovai/open-ontologies:latest@sha256:2932c10682eac29ccf840a6bd6c4c7c82c5ce770ad9e94697d44057187452530" + }, "ghcr.io/github/gh-aw-firewall/agent:0.27.43": { "image": "ghcr.io/github/gh-aw-firewall/agent:0.27.43", "digest": "sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6", @@ -239,11 +244,6 @@ "image": "python:alpine", "digest": "sha256:26730869004e2b9c4b9ad09cab8625e81d256d1ce97e72df5520e806b1709f92", "pinned_image": "python:alpine@sha256:26730869004e2b9c4b9ad09cab8625e81d256d1ce97e72df5520e806b1709f92" - }, - "ghcr.io/fabio-rovai/open-ontologies:latest": { - "image": "ghcr.io/fabio-rovai/open-ontologies:latest", - "digest": "sha256:2932c10682eac29ccf840a6bd6c4c7c82c5ce770ad9e94697d44057187452530", - "pinned_image": "ghcr.io/fabio-rovai/open-ontologies:latest@sha256:2932c10682eac29ccf840a6bd6c4c7c82c5ce770ad9e94697d44057187452530" } } } diff --git a/.github/workflows/deep-report.lock.yml b/.github/workflows/deep-report.lock.yml index 30c3539589d..a23d4e6290a 100644 --- a/.github/workflows/deep-report.lock.yml +++ b/.github/workflows/deep-report.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"edcdc0f024ee3ab0298171a1d7c1ffd8e07a37a34de8a16a6e990725e478d758","body_hash":"2b2cec4250bfd0b0e95df7aa5981e3dd0f3945bd4a83b41079f970a8559a8c7d","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.220"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f13719aca64a4582c69af8e986fec6dde2b49ee1c8cbc1c90d37e19967d02f44","body_hash":"def30d39a7c5d17c62c1b7971437f8a66c3a372cea3444a1fa2bd967ec5d0a51","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.220"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"b7ad1dad31e06c5925ef5d2fc7ad053ef454303e","version":"v7.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"docker/build-push-action","sha":"53b7df96c91f9c12dcc8a07bcb9ccacbed38856a","version":"v7.3.0"},{"repo":"docker/setup-buildx-action","sha":"bb05f3f5519dd87d3ba754cc423b652a5edd6d2c","version":"v4.2.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43","digest":"sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43","digest":"sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43","digest":"sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.43@sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43","digest":"sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.7","digest":"sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"},{"image":"node:lts-alpine","digest":"sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43","pinned_image":"node:lts-alpine@sha256:d32cdf619f63fe0471182d08996dd516c6275bb5fd31ae06e55a570bd9e1ad43"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -1093,12 +1093,63 @@ jobs: - name: Write MCP Scripts Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-scripts/logs" - cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/tools.json" << 'GH_AW_MCP_SCRIPTS_TOOLS_f5a5e76e6c546226_EOF' + cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/tools.json" << 'GH_AW_MCP_SCRIPTS_TOOLS_c9d8790164693307_EOF' { "serverName": "mcpscripts", "version": "1.0.0", "logDir": "${RUNNER_TEMP}/gh-aw/mcp-scripts/logs", "tools": [ + { + "name": "get_file_contents_excerpt", + "description": "Read a bounded excerpt from a repository file without returning the whole file. Supports byteOffset/maxBytes and optional startLine/endLine filtering within the fetched byte window.", + "inputSchema": { + "properties": { + "byteOffset": { + "description": "Zero-based byte offset to start reading from (default: 0)", + "type": "number" + }, + "endLine": { + "description": "Optional one-based line number to stop returning within the fetched byte window", + "type": "number" + }, + "maxBytes": { + "description": "Maximum bytes to fetch before line filtering (1-200000, default: 20000)", + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "path": { + "description": "Path to the file in the repository", + "type": "string" + }, + "ref": { + "description": "Git ref to read from (defaults to GITHUB_SHA, or the repository default branch when unavailable)", + "type": "string" + }, + "repo": { + "description": "Repository name", + "type": "string" + }, + "startLine": { + "description": "Optional one-based line number to start returning within the fetched byte window", + "type": "number" + } + }, + "required": [ + "owner", + "path", + "repo" + ], + "type": "object" + }, + "handler": "get_file_contents_excerpt.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, { "name": "list_label", "description": "List labels in a GitHub repository with perPage pagination support. Returns labels array, item_count, per_page, and page. Defaults to perPage=10 to avoid large responses.", @@ -1169,7 +1220,7 @@ jobs: } ] } - GH_AW_MCP_SCRIPTS_TOOLS_f5a5e76e6c546226_EOF + GH_AW_MCP_SCRIPTS_TOOLS_c9d8790164693307_EOF cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/mcp-server.cjs" << 'GH_AW_MCP_SCRIPTS_SERVER_e0a10bf70e571677_EOF' const path = require("path"); const { startHttpServer } = require("./mcp_scripts_mcp_server_http.cjs"); @@ -1189,6 +1240,130 @@ jobs: - name: Write MCP Scripts Tool Files run: | + cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/get_file_contents_excerpt.sh" << 'GH_AW_MCP_SCRIPTS_SH_GET_FILE_CONTENTS_EXCERPT_cb07b1b5d5aa8aa9_EOF' + #!/bin/bash + # Auto-generated mcp-script tool: get_file_contents_excerpt + # Read a bounded excerpt from a repository file without returning the whole file. Supports byteOffset/maxBytes and optional startLine/endLine filtering within the fetched byte window. + + set +o histexpand + set -euo pipefail + + set -euo pipefail + + OWNER="${INPUT_OWNER:-}" + REPO="${INPUT_REPO:-}" + PATH_IN_REPO="${INPUT_PATH:-}" + REF="${INPUT_REF:-}" + if [[ -z "$REF" ]]; then + if [[ "${OWNER}/${REPO}" == "${GITHUB_REPOSITORY:-}" ]]; then + REF="${GITHUB_SHA:-}" + fi + fi + BYTE_OFFSET="${INPUT_BYTEOFFSET:-0}" + MAX_BYTES="${INPUT_MAXBYTES:-20000}" + START_LINE="${INPUT_STARTLINE:-}" + END_LINE="${INPUT_ENDLINE:-}" + + if [[ -z "$OWNER" ]]; then + echo '{"error": "owner is required"}' >&2 + exit 1 + fi + + if [[ -z "$REPO" ]]; then + echo '{"error": "repo is required"}' >&2 + exit 1 + fi + + if [[ -z "$PATH_IN_REPO" ]]; then + echo '{"error": "path is required"}' >&2 + exit 1 + fi + + if ! [[ "$BYTE_OFFSET" =~ ^[0-9]+$ ]]; then + echo '{"error": "byteOffset must be a non-negative integer"}' >&2 + exit 1 + fi + + if ! [[ "$MAX_BYTES" =~ ^[0-9]+$ ]] || [[ "$MAX_BYTES" -lt 1 ]] || [[ "$MAX_BYTES" -gt 200000 ]]; then + echo '{"error": "maxBytes must be between 1 and 200000"}' >&2 + exit 1 + fi + + if [[ -n "$START_LINE" ]] && { ! [[ "$START_LINE" =~ ^[0-9]+$ ]] || [[ "$START_LINE" -lt 1 ]]; }; then + echo '{"error": "startLine must be a positive integer"}' >&2 + exit 1 + fi + + if [[ -n "$END_LINE" ]] && { ! [[ "$END_LINE" =~ ^[0-9]+$ ]] || [[ "$END_LINE" -lt 1 ]]; }; then + echo '{"error": "endLine must be a positive integer"}' >&2 + exit 1 + fi + + if [[ -n "$START_LINE" && -n "$END_LINE" && "$END_LINE" -lt "$START_LINE" ]]; then + echo '{"error": "endLine must be greater than or equal to startLine"}' >&2 + exit 1 + fi + + if [[ -z "$REF" ]]; then + REF=$(gh repo view "${OWNER}/${REPO}" --json defaultBranchRef --jq '.defaultBranchRef.name') + fi + + ENCODED_PATH=$(python3 -c "import sys, urllib.parse; print('/'.join(urllib.parse.quote(p, safe='') for p in sys.argv[1].split('/')))" "$PATH_IN_REPO") + + RAW_FILE=$(mktemp) + trap 'rm -f "$RAW_FILE"' EXIT + + BYTE_END=$((BYTE_OFFSET + MAX_BYTES)) + export OWNER REPO PATH_IN_REPO REF BYTE_OFFSET MAX_BYTES START_LINE END_LINE + gh api \ + --method GET \ + -H "Accept: application/vnd.github.raw" \ + -H "Range: bytes=${BYTE_OFFSET}-${BYTE_END}" \ + "repos/${OWNER}/${REPO}/contents/${ENCODED_PATH}" \ + -f "ref=${REF}" > "$RAW_FILE" + + python3 - "$RAW_FILE" <<'PY' + import json + import os + import sys + + raw_path = sys.argv[1] + max_bytes = int(os.environ["MAX_BYTES"]) + byte_offset = int(os.environ["BYTE_OFFSET"]) + start_line = os.environ.get("START_LINE") or "" + end_line = os.environ.get("END_LINE") or "" + + data = open(raw_path, "rb").read() + truncated = len(data) > max_bytes + data = data[:max_bytes] + text = data.decode("utf-8", errors="replace") + + line_start = None + line_end = None + if start_line or end_line: + line_start = int(start_line) if start_line else 1 + line_end = int(end_line) if end_line else None + lines = text.splitlines(keepends=True) + text = "".join(lines[line_start - 1:line_end]) + + print(json.dumps({ + "owner": os.environ["OWNER"], + "repo": os.environ["REPO"], + "path": os.environ["PATH_IN_REPO"], + "ref": os.environ["REF"], + "byte_offset": byte_offset, + "max_bytes": max_bytes, + "start_line": line_start, + "end_line": line_end, + "content": text, + "content_bytes": len(text.encode("utf-8")), + "truncated_by_max_bytes": truncated, + })) + PY + + + GH_AW_MCP_SCRIPTS_SH_GET_FILE_CONTENTS_EXCERPT_cb07b1b5d5aa8aa9_EOF + chmod +x "${RUNNER_TEMP}/gh-aw/mcp-scripts/get_file_contents_excerpt.sh" cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/list_label.sh" << 'GH_AW_MCP_SCRIPTS_SH_LIST_LABEL_e74f76f3215a6b0b_EOF' #!/bin/bash # Auto-generated mcp-script tool: list_label diff --git a/.github/workflows/github-mcp-structural-analysis.lock.yml b/.github/workflows/github-mcp-structural-analysis.lock.yml index 838b8f6a1bb..16ab420cb76 100644 --- a/.github/workflows/github-mcp-structural-analysis.lock.yml +++ b/.github/workflows/github-mcp-structural-analysis.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"3e23111997930b5282b5fe3fba686b4f3beedfa0c71b4c3da77d211de47697d0","body_hash":"52df15ed546c1eb02b6a3d2720f1cd1f64e426f770b17e11c5242f10f55c24bb","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.220"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5ef5dd15d1bb95d0504de3a8dd02389b34f72525528add15f510bd4f43587863","body_hash":"3dd0978c427bcf0f933b3726db3590e58dffda95a7fdbd12bf6b27c9b831f4c3","strict":true,"agent_id":"claude","engine_versions":{"claude":"2.1.220"}} # gh-aw-manifest: {"version":1,"secrets":["ANTHROPIC_API_KEY","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/setup-python","sha":"5fda3b95a4ea91299a34e894583c3862153e4b97","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43","digest":"sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.43@sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43","digest":"sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.43@sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43","digest":"sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.43@sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.7","digest":"sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.7@sha256:7545220a9aca134b71e51193ee0eaf4c50756ebf8fbd25a63ae7556e62815c00"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -747,12 +747,63 @@ jobs: - name: Write MCP Scripts Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-scripts/logs" - cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/tools.json" << 'GH_AW_MCP_SCRIPTS_TOOLS_f5a5e76e6c546226_EOF' + cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/tools.json" << 'GH_AW_MCP_SCRIPTS_TOOLS_c9d8790164693307_EOF' { "serverName": "mcpscripts", "version": "1.0.0", "logDir": "${RUNNER_TEMP}/gh-aw/mcp-scripts/logs", "tools": [ + { + "name": "get_file_contents_excerpt", + "description": "Read a bounded excerpt from a repository file without returning the whole file. Supports byteOffset/maxBytes and optional startLine/endLine filtering within the fetched byte window.", + "inputSchema": { + "properties": { + "byteOffset": { + "description": "Zero-based byte offset to start reading from (default: 0)", + "type": "number" + }, + "endLine": { + "description": "Optional one-based line number to stop returning within the fetched byte window", + "type": "number" + }, + "maxBytes": { + "description": "Maximum bytes to fetch before line filtering (1-200000, default: 20000)", + "type": "number" + }, + "owner": { + "description": "Repository owner (username or organization)", + "type": "string" + }, + "path": { + "description": "Path to the file in the repository", + "type": "string" + }, + "ref": { + "description": "Git ref to read from (defaults to GITHUB_SHA, or the repository default branch when unavailable)", + "type": "string" + }, + "repo": { + "description": "Repository name", + "type": "string" + }, + "startLine": { + "description": "Optional one-based line number to start returning within the fetched byte window", + "type": "number" + } + }, + "required": [ + "owner", + "path", + "repo" + ], + "type": "object" + }, + "handler": "get_file_contents_excerpt.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, { "name": "list_label", "description": "List labels in a GitHub repository with perPage pagination support. Returns labels array, item_count, per_page, and page. Defaults to perPage=10 to avoid large responses.", @@ -823,7 +874,7 @@ jobs: } ] } - GH_AW_MCP_SCRIPTS_TOOLS_f5a5e76e6c546226_EOF + GH_AW_MCP_SCRIPTS_TOOLS_c9d8790164693307_EOF cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/mcp-server.cjs" << 'GH_AW_MCP_SCRIPTS_SERVER_e0a10bf70e571677_EOF' const path = require("path"); const { startHttpServer } = require("./mcp_scripts_mcp_server_http.cjs"); @@ -843,6 +894,130 @@ jobs: - name: Write MCP Scripts Tool Files run: | + cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/get_file_contents_excerpt.sh" << 'GH_AW_MCP_SCRIPTS_SH_GET_FILE_CONTENTS_EXCERPT_cb07b1b5d5aa8aa9_EOF' + #!/bin/bash + # Auto-generated mcp-script tool: get_file_contents_excerpt + # Read a bounded excerpt from a repository file without returning the whole file. Supports byteOffset/maxBytes and optional startLine/endLine filtering within the fetched byte window. + + set +o histexpand + set -euo pipefail + + set -euo pipefail + + OWNER="${INPUT_OWNER:-}" + REPO="${INPUT_REPO:-}" + PATH_IN_REPO="${INPUT_PATH:-}" + REF="${INPUT_REF:-}" + if [[ -z "$REF" ]]; then + if [[ "${OWNER}/${REPO}" == "${GITHUB_REPOSITORY:-}" ]]; then + REF="${GITHUB_SHA:-}" + fi + fi + BYTE_OFFSET="${INPUT_BYTEOFFSET:-0}" + MAX_BYTES="${INPUT_MAXBYTES:-20000}" + START_LINE="${INPUT_STARTLINE:-}" + END_LINE="${INPUT_ENDLINE:-}" + + if [[ -z "$OWNER" ]]; then + echo '{"error": "owner is required"}' >&2 + exit 1 + fi + + if [[ -z "$REPO" ]]; then + echo '{"error": "repo is required"}' >&2 + exit 1 + fi + + if [[ -z "$PATH_IN_REPO" ]]; then + echo '{"error": "path is required"}' >&2 + exit 1 + fi + + if ! [[ "$BYTE_OFFSET" =~ ^[0-9]+$ ]]; then + echo '{"error": "byteOffset must be a non-negative integer"}' >&2 + exit 1 + fi + + if ! [[ "$MAX_BYTES" =~ ^[0-9]+$ ]] || [[ "$MAX_BYTES" -lt 1 ]] || [[ "$MAX_BYTES" -gt 200000 ]]; then + echo '{"error": "maxBytes must be between 1 and 200000"}' >&2 + exit 1 + fi + + if [[ -n "$START_LINE" ]] && { ! [[ "$START_LINE" =~ ^[0-9]+$ ]] || [[ "$START_LINE" -lt 1 ]]; }; then + echo '{"error": "startLine must be a positive integer"}' >&2 + exit 1 + fi + + if [[ -n "$END_LINE" ]] && { ! [[ "$END_LINE" =~ ^[0-9]+$ ]] || [[ "$END_LINE" -lt 1 ]]; }; then + echo '{"error": "endLine must be a positive integer"}' >&2 + exit 1 + fi + + if [[ -n "$START_LINE" && -n "$END_LINE" && "$END_LINE" -lt "$START_LINE" ]]; then + echo '{"error": "endLine must be greater than or equal to startLine"}' >&2 + exit 1 + fi + + if [[ -z "$REF" ]]; then + REF=$(gh repo view "${OWNER}/${REPO}" --json defaultBranchRef --jq '.defaultBranchRef.name') + fi + + ENCODED_PATH=$(python3 -c "import sys, urllib.parse; print('/'.join(urllib.parse.quote(p, safe='') for p in sys.argv[1].split('/')))" "$PATH_IN_REPO") + + RAW_FILE=$(mktemp) + trap 'rm -f "$RAW_FILE"' EXIT + + BYTE_END=$((BYTE_OFFSET + MAX_BYTES)) + export OWNER REPO PATH_IN_REPO REF BYTE_OFFSET MAX_BYTES START_LINE END_LINE + gh api \ + --method GET \ + -H "Accept: application/vnd.github.raw" \ + -H "Range: bytes=${BYTE_OFFSET}-${BYTE_END}" \ + "repos/${OWNER}/${REPO}/contents/${ENCODED_PATH}" \ + -f "ref=${REF}" > "$RAW_FILE" + + python3 - "$RAW_FILE" <<'PY' + import json + import os + import sys + + raw_path = sys.argv[1] + max_bytes = int(os.environ["MAX_BYTES"]) + byte_offset = int(os.environ["BYTE_OFFSET"]) + start_line = os.environ.get("START_LINE") or "" + end_line = os.environ.get("END_LINE") or "" + + data = open(raw_path, "rb").read() + truncated = len(data) > max_bytes + data = data[:max_bytes] + text = data.decode("utf-8", errors="replace") + + line_start = None + line_end = None + if start_line or end_line: + line_start = int(start_line) if start_line else 1 + line_end = int(end_line) if end_line else None + lines = text.splitlines(keepends=True) + text = "".join(lines[line_start - 1:line_end]) + + print(json.dumps({ + "owner": os.environ["OWNER"], + "repo": os.environ["REPO"], + "path": os.environ["PATH_IN_REPO"], + "ref": os.environ["REF"], + "byte_offset": byte_offset, + "max_bytes": max_bytes, + "start_line": line_start, + "end_line": line_end, + "content": text, + "content_bytes": len(text.encode("utf-8")), + "truncated_by_max_bytes": truncated, + })) + PY + + + GH_AW_MCP_SCRIPTS_SH_GET_FILE_CONTENTS_EXCERPT_cb07b1b5d5aa8aa9_EOF + chmod +x "${RUNNER_TEMP}/gh-aw/mcp-scripts/get_file_contents_excerpt.sh" cat > "${RUNNER_TEMP}/gh-aw/mcp-scripts/list_label.sh" << 'GH_AW_MCP_SCRIPTS_SH_LIST_LABEL_e74f76f3215a6b0b_EOF' #!/bin/bash # Auto-generated mcp-script tool: list_label diff --git a/.github/workflows/github-mcp-structural-analysis.md b/.github/workflows/github-mcp-structural-analysis.md index 140c1450a3a..20eddbab8b9 100644 --- a/.github/workflows/github-mcp-structural-analysis.md +++ b/.github/workflows/github-mcp-structural-analysis.md @@ -87,7 +87,7 @@ Record this prompt-only source separately: - **workflow_context**: `` - Injected workflow identity metadata; no MCP call required 1. **context**: `get_teams` - Inspect team-awareness data with `org` set to the repository owner -2. **repos**: `get_file_contents` - Get a small file (README.md or similar) +2. **repos**: `get_file_contents_excerpt` (mcp-scripts wrapper) - Get a bounded excerpt from a file (for example `README.md` with `maxBytes: 4000`) instead of calling the built-in `get_file_contents` tool, which has no range/excerpt mode and can return large boilerplate-heavy files 3. **issues**: `list_issues` - List issues with perPage=1 4. **pull_requests**: `list_pull_requests` - List PRs with perPage=1 5. **actions**: `list_workflows` (mcp-scripts wrapper) - List workflows with `perPage: 1` (the built-in GitHub MCP `list_workflows` tool uses snake_case `per_page` and silently ignores camelCase `perPage`; the mcp-scripts wrapper imported via `shared/github-mcp-pagination-wrappers.md` respects `perPage`) diff --git a/.github/workflows/shared/github-mcp-pagination-wrappers.md b/.github/workflows/shared/github-mcp-pagination-wrappers.md index f54c2c2783e..4cbfc887c66 100644 --- a/.github/workflows/shared/github-mcp-pagination-wrappers.md +++ b/.github/workflows/shared/github-mcp-pagination-wrappers.md @@ -121,12 +121,164 @@ mcp-scripts: per_page: $per_page, page: $page }' + + get_file_contents_excerpt: + description: "Read a bounded excerpt from a repository file without returning the whole file. Supports byteOffset/maxBytes and optional startLine/endLine filtering within the fetched byte window." + inputs: + owner: + type: string + description: "Repository owner (username or organization)" + required: true + repo: + type: string + description: "Repository name" + required: true + path: + type: string + description: "Path to the file in the repository" + required: true + ref: + type: string + description: "Git ref to read from (defaults to GITHUB_SHA, or the repository default branch when unavailable)" + required: false + byteOffset: + type: number + description: "Zero-based byte offset to start reading from (default: 0)" + required: false + maxBytes: + type: number + description: "Maximum bytes to fetch before line filtering (1-200000, default: 20000)" + required: false + startLine: + type: number + description: "Optional one-based line number to start returning within the fetched byte window" + required: false + endLine: + type: number + description: "Optional one-based line number to stop returning within the fetched byte window" + required: false + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + OWNER="${INPUT_OWNER:-}" + REPO="${INPUT_REPO:-}" + PATH_IN_REPO="${INPUT_PATH:-}" + REF="${INPUT_REF:-}" + if [[ -z "$REF" ]]; then + if [[ "${OWNER}/${REPO}" == "${GITHUB_REPOSITORY:-}" ]]; then + REF="${GITHUB_SHA:-}" + fi + fi + BYTE_OFFSET="${INPUT_BYTEOFFSET:-0}" + MAX_BYTES="${INPUT_MAXBYTES:-20000}" + START_LINE="${INPUT_STARTLINE:-}" + END_LINE="${INPUT_ENDLINE:-}" + + if [[ -z "$OWNER" ]]; then + echo '{"error": "owner is required"}' >&2 + exit 1 + fi + + if [[ -z "$REPO" ]]; then + echo '{"error": "repo is required"}' >&2 + exit 1 + fi + + if [[ -z "$PATH_IN_REPO" ]]; then + echo '{"error": "path is required"}' >&2 + exit 1 + fi + + if ! [[ "$BYTE_OFFSET" =~ ^[0-9]+$ ]]; then + echo '{"error": "byteOffset must be a non-negative integer"}' >&2 + exit 1 + fi + + if ! [[ "$MAX_BYTES" =~ ^[0-9]+$ ]] || [[ "$MAX_BYTES" -lt 1 ]] || [[ "$MAX_BYTES" -gt 200000 ]]; then + echo '{"error": "maxBytes must be between 1 and 200000"}' >&2 + exit 1 + fi + + if [[ -n "$START_LINE" ]] && { ! [[ "$START_LINE" =~ ^[0-9]+$ ]] || [[ "$START_LINE" -lt 1 ]]; }; then + echo '{"error": "startLine must be a positive integer"}' >&2 + exit 1 + fi + + if [[ -n "$END_LINE" ]] && { ! [[ "$END_LINE" =~ ^[0-9]+$ ]] || [[ "$END_LINE" -lt 1 ]]; }; then + echo '{"error": "endLine must be a positive integer"}' >&2 + exit 1 + fi + + if [[ -n "$START_LINE" && -n "$END_LINE" && "$END_LINE" -lt "$START_LINE" ]]; then + echo '{"error": "endLine must be greater than or equal to startLine"}' >&2 + exit 1 + fi + + if [[ -z "$REF" ]]; then + REF=$(gh repo view "${OWNER}/${REPO}" --json defaultBranchRef --jq '.defaultBranchRef.name') + fi + + ENCODED_PATH=$(python3 -c "import sys, urllib.parse; print('/'.join(urllib.parse.quote(p, safe='') for p in sys.argv[1].split('/')))" "$PATH_IN_REPO") + + RAW_FILE=$(mktemp) + trap 'rm -f "$RAW_FILE"' EXIT + + BYTE_END=$((BYTE_OFFSET + MAX_BYTES)) + export OWNER REPO PATH_IN_REPO REF BYTE_OFFSET MAX_BYTES START_LINE END_LINE + gh api \ + --method GET \ + -H "Accept: application/vnd.github.raw" \ + -H "Range: bytes=${BYTE_OFFSET}-${BYTE_END}" \ + "repos/${OWNER}/${REPO}/contents/${ENCODED_PATH}" \ + -f "ref=${REF}" > "$RAW_FILE" + + python3 - "$RAW_FILE" <<'PY' + import json + import os + import sys + + raw_path = sys.argv[1] + max_bytes = int(os.environ["MAX_BYTES"]) + byte_offset = int(os.environ["BYTE_OFFSET"]) + start_line = os.environ.get("START_LINE") or "" + end_line = os.environ.get("END_LINE") or "" + + data = open(raw_path, "rb").read() + truncated = len(data) > max_bytes + data = data[:max_bytes] + text = data.decode("utf-8", errors="replace") + + line_start = None + line_end = None + if start_line or end_line: + line_start = int(start_line) if start_line else 1 + line_end = int(end_line) if end_line else None + lines = text.splitlines(keepends=True) + text = "".join(lines[line_start - 1:line_end]) + + print(json.dumps({ + "owner": os.environ["OWNER"], + "repo": os.environ["REPO"], + "path": os.environ["PATH_IN_REPO"], + "ref": os.environ["REF"], + "byte_offset": byte_offset, + "max_bytes": max_bytes, + "start_line": line_start, + "end_line": line_end, + "content": text, + "content_bytes": len(text.encode("utf-8")), + "truncated_by_max_bytes": truncated, + })) + PY ---