From 2b380cbd68913f7f2387c812a1e80a69dd9e06f5 Mon Sep 17 00:00:00 2001 From: Norrie Taylor Date: Thu, 21 May 2026 00:55:42 -0700 Subject: [PATCH] fix(chore-style): forbid create_issue in autofix mode; agent must open PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #30. In autofix mode the Copilot agent was calling create_issue instead of create_pull_request, producing a duplicate of any open report-mode issue, never opening the autofix PR, and never closing the source issue. Reproduced on gominimal/spectacles-test run 26210555486 (chore-style-python autofix on an F403 violation). Root cause: the safe-outputs allowlist correctly includes both create-issue and create-pull-request so the chore can run in either mode, but the prose never tells the agent which one to call per mode. The agent fell back to create_issue. Fix: add a "Mode → safe-output contract (READ FIRST)" section to each chore-style-* workflow that names the required tool per mode and explicitly forbids the wrong tool, plus a matching "What you must not do" entry. The contract names both the YAML allowlist key (`create- pull-request`) and the runtime tool name (`create_pull_request`). Allowlist-level gating (templated `max:` expression that resolves to 0 in the wrong mode) was attempted but is not currently supportable in gh-aw: every ternary form (`cond && '0' || '1'`, `fromJSON('{"k":"v"}') [inputs.x]`) renders into the safe-outputs JSON env var with characters actionlint cannot lex (`&&` → `&&`; nested `\"` inside `fromJSON()`). The YAML frontmatter now carries a comment explaining this so a future contributor does not re-attempt the same approach. Applied identically to chore-style-{python,toml,rust,go,ncl} — they all share the report/autofix mode template. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/chore-style-go.lock.yml | 60 ++++++++++++++----- .github/workflows/chore-style-go.md | 41 ++++++++++++- .github/workflows/chore-style-ncl.lock.yml | 60 ++++++++++++++----- .github/workflows/chore-style-ncl.md | 41 ++++++++++++- .github/workflows/chore-style-python.lock.yml | 60 ++++++++++++++----- .github/workflows/chore-style-python.md | 41 ++++++++++++- .github/workflows/chore-style-rust.lock.yml | 60 ++++++++++++++----- .github/workflows/chore-style-rust.md | 41 ++++++++++++- .github/workflows/chore-style-toml.lock.yml | 60 ++++++++++++++----- .github/workflows/chore-style-toml.md | 41 ++++++++++++- workflows/chore-style-go.md | 41 ++++++++++++- workflows/chore-style-ncl.md | 41 ++++++++++++- workflows/chore-style-python.md | 41 ++++++++++++- workflows/chore-style-rust.md | 41 ++++++++++++- workflows/chore-style-toml.md | 41 ++++++++++++- 15 files changed, 620 insertions(+), 90 deletions(-) diff --git a/.github/workflows/chore-style-go.lock.yml b/.github/workflows/chore-style-go.lock.yml index 7dc8c42..ca59f3c 100644 --- a/.github/workflows/chore-style-go.lock.yml +++ b/.github/workflows/chore-style-go.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"75ac690e8edd39c77450c4ec6cff5dd27b6b94ce07417d4a767a1402b207503f","compiler_version":"v0.74.3","agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6f1ad8d13c8e7665972c58c35e827a2a203758d94d27e89ad9d6cc89ea8b1c38","compiler_version":"v0.74.3","agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"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":"v5","version":"v5"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -235,23 +235,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF' + cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF' - GH_AW_PROMPT_dbf277e45ce618d1_EOF + GH_AW_PROMPT_75feba9e090dc7aa_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF' + cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF' Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_dbf277e45ce618d1_EOF + GH_AW_PROMPT_75feba9e090dc7aa_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF' + cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF' - GH_AW_PROMPT_dbf277e45ce618d1_EOF + GH_AW_PROMPT_75feba9e090dc7aa_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF' + cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -280,9 +280,9 @@ jobs: {{/if}} - GH_AW_PROMPT_dbf277e45ce618d1_EOF + GH_AW_PROMPT_75feba9e090dc7aa_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_dbf277e45ce618d1_EOF' + cat << 'GH_AW_PROMPT_75feba9e090dc7aa_EOF' @@ -765,6 +765,30 @@ jobs: You are the Go style agent. Read `inputs.mode` and act accordingly. + ## Mode → safe-output contract (READ FIRST) + + The safe-output tool you call MUST match `inputs.mode`. Picking the wrong + tool is the defect tracked in issue #30 and is the single most important + rule in this prompt. + + - `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. + - `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + + If you find yourself about to call `create_issue` while `inputs.mode == + autofix`, stop and re-read this section. + ## Mode: report 1. `gofmt -l .` — list files needing format (empty output means clean). @@ -802,7 +826,9 @@ jobs: the PR**; staticcheck has no `--fix` mode and surfaces semantic issues. - `go build ./...` — must exit 0. - `go test ./...` — must exit 0. - 4. Open one PR via `create-pull-request`: + 4. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:go] auto-applied gofmt + goimports`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:go`, `agent:autofix`. @@ -820,8 +846,10 @@ jobs: - Do not modify `go.mod` or `go.sum`. - Do not auto-apply go vet or staticcheck suggestions. - Do not open more than one PR or issue per run. + - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. + - Do not call `create_pull_request` when `inputs.mode == report`. - GH_AW_PROMPT_dbf277e45ce618d1_EOF + GH_AW_PROMPT_75feba9e090dc7aa_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1026,9 +1054,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7729c44e1703fffc_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_63d6cb2d016e54fc_EOF' {"create_issue":{"labels":["agent:lint:go"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:go","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"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","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_7729c44e1703fffc_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_63d6cb2d016e54fc_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1333,7 +1361,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_14c58b6124db2869_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_00c9308fa251b77e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -1374,7 +1402,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_14c58b6124db2869_EOF + GH_AW_MCP_CONFIG_00c9308fa251b77e_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/chore-style-go.md b/.github/workflows/chore-style-go.md index a1df259..d9dc8b3 100644 --- a/.github/workflows/chore-style-go.md +++ b/.github/workflows/chore-style-go.md @@ -40,6 +40,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -87,6 +98,30 @@ Behavior summary: You are the Go style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Mode: report 1. `gofmt -l .` — list files needing format (empty output means clean). @@ -124,7 +159,9 @@ Apply dedup before emitting. the PR**; staticcheck has no `--fix` mode and surfaces semantic issues. - `go build ./...` — must exit 0. - `go test ./...` — must exit 0. -4. Open one PR via `create-pull-request`: +4. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:go] auto-applied gofmt + goimports`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:go`, `agent:autofix`. @@ -142,3 +179,5 @@ Apply dedup before emitting. - Do not modify `go.mod` or `go.sum`. - Do not auto-apply go vet or staticcheck suggestions. - Do not open more than one PR or issue per run. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/.github/workflows/chore-style-ncl.lock.yml b/.github/workflows/chore-style-ncl.lock.yml index 7c3f8d8..f79761d 100644 --- a/.github/workflows/chore-style-ncl.lock.yml +++ b/.github/workflows/chore-style-ncl.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"98a0b3ccd6685d3286de63b3b2ef4632457ba84e74f1efe8f5f68643d9c48d71","compiler_version":"v0.74.3","agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"6b26e31c87143ef4a69781e136dd352bc7c6194086b8f4a0731099e70617be19","compiler_version":"v0.74.3","agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -233,23 +233,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF' + cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF' - GH_AW_PROMPT_7bf3b7bc267bb54a_EOF + GH_AW_PROMPT_dbc7efee147b3557_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF' + cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF' Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_7bf3b7bc267bb54a_EOF + GH_AW_PROMPT_dbc7efee147b3557_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF' + cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF' - GH_AW_PROMPT_7bf3b7bc267bb54a_EOF + GH_AW_PROMPT_dbc7efee147b3557_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF' + cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -278,9 +278,9 @@ jobs: {{/if}} - GH_AW_PROMPT_7bf3b7bc267bb54a_EOF + GH_AW_PROMPT_dbc7efee147b3557_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_7bf3b7bc267bb54a_EOF' + cat << 'GH_AW_PROMPT_dbc7efee147b3557_EOF' @@ -690,6 +690,30 @@ jobs: You are the Nickel style agent. Read `inputs.mode` and act accordingly. + ## Mode → safe-output contract (READ FIRST) + + The safe-output tool you call MUST match `inputs.mode`. Picking the wrong + tool is the defect tracked in issue #30 and is the single most important + rule in this prompt. + + - `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. + - `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + + If you find yourself about to call `create_issue` while `inputs.mode == + autofix`, stop and re-read this section. + ## Scope Every `*.ncl` file under the repo root, excluding paths in `.gitignore` and @@ -726,7 +750,9 @@ jobs: - `nickel format --check` on each file — must exit 0. - `nickel typecheck` on each file — must exit 0. **If typecheck fails, do not open the PR**; type errors are reported, not auto-fixed. - 3. Open one PR via `create-pull-request`: + 3. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:ncl] auto-applied nickel format`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:ncl`, `agent:autofix`. @@ -744,8 +770,10 @@ jobs: - Do not auto-apply typecheck fixes. - Do not open more than one PR or issue per run. - Do not modify files outside the in-scope set. + - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. + - Do not call `create_pull_request` when `inputs.mode == report`. - GH_AW_PROMPT_7bf3b7bc267bb54a_EOF + GH_AW_PROMPT_dbc7efee147b3557_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -950,9 +978,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_de6a1440474a5e7a_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2865f2ee842c780c_EOF' {"create_issue":{"labels":["agent:lint:ncl"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:ncl","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"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","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_de6a1440474a5e7a_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_2865f2ee842c780c_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1257,7 +1285,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_1fbbe8ba343fc38b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_9cad70d2fcd0078b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -1298,7 +1326,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_1fbbe8ba343fc38b_EOF + GH_AW_MCP_CONFIG_9cad70d2fcd0078b_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/chore-style-ncl.md b/.github/workflows/chore-style-ncl.md index 4ecefaf..5c3a773 100644 --- a/.github/workflows/chore-style-ncl.md +++ b/.github/workflows/chore-style-ncl.md @@ -39,6 +39,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -81,6 +92,30 @@ Behavior summary: You are the Nickel style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Scope Every `*.ncl` file under the repo root, excluding paths in `.gitignore` and @@ -117,7 +152,9 @@ Apply dedup before emitting. - `nickel format --check` on each file — must exit 0. - `nickel typecheck` on each file — must exit 0. **If typecheck fails, do not open the PR**; type errors are reported, not auto-fixed. -3. Open one PR via `create-pull-request`: +3. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:ncl] auto-applied nickel format`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:ncl`, `agent:autofix`. @@ -135,3 +172,5 @@ Apply dedup before emitting. - Do not auto-apply typecheck fixes. - Do not open more than one PR or issue per run. - Do not modify files outside the in-scope set. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/.github/workflows/chore-style-python.lock.yml b/.github/workflows/chore-style-python.lock.yml index b0b3d7c..d4162ca 100644 --- a/.github/workflows/chore-style-python.lock.yml +++ b/.github/workflows/chore-style-python.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"88099074474cfc09845d8b84c7f0f97d0bb0076fcb2b039ce95c034d8a839f99","compiler_version":"v0.74.3","agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"53646a5bc5496eb158001d4acd0b58004596b7169547c21a202bd5d4946711a5","compiler_version":"v0.74.3","agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"astral-sh/setup-uv","sha":"v3","version":"v3"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -235,23 +235,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_576e86da4707b909_EOF' + cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF' - GH_AW_PROMPT_576e86da4707b909_EOF + GH_AW_PROMPT_2e88897e0e4c235c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_576e86da4707b909_EOF' + cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF' Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_576e86da4707b909_EOF + GH_AW_PROMPT_2e88897e0e4c235c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_576e86da4707b909_EOF' + cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF' - GH_AW_PROMPT_576e86da4707b909_EOF + GH_AW_PROMPT_2e88897e0e4c235c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_576e86da4707b909_EOF' + cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -280,9 +280,9 @@ jobs: {{/if}} - GH_AW_PROMPT_576e86da4707b909_EOF + GH_AW_PROMPT_2e88897e0e4c235c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_576e86da4707b909_EOF' + cat << 'GH_AW_PROMPT_2e88897e0e4c235c_EOF' @@ -779,6 +779,30 @@ jobs: You are the Python style agent. Read `inputs.mode` and act accordingly. + ## Mode → safe-output contract (READ FIRST) + + The safe-output tool you call MUST match `inputs.mode`. Picking the wrong + tool is the defect tracked in issue #30 and is the single most important + rule in this prompt. + + - `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. + - `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + + If you find yourself about to call `create_issue` while `inputs.mode == + autofix`, stop and re-read this section. + ## Mode: report 1. `uv sync --frozen` to provision the environment. @@ -816,7 +840,9 @@ jobs: even if ruff is clean; mypy has no `--fix` mode and unfixed type errors carry semantic risk. - `uv run pytest` — must exit 0. - 5. Open one PR via `create-pull-request`: + 5. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:python] auto-applied ruff format + lint fixes`. - Body: summary of files touched, count of format vs lint fixes, `Closes #` if a matching `agent:lint:python` issue is open. @@ -836,8 +862,10 @@ jobs: - Do not auto-apply mypy fixes (mypy has no `--fix` mode; do not synthesize one). - Do not open more than one PR or issue per run. - Do not skip the pytest verification step in autofix mode. + - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. + - Do not call `create_pull_request` when `inputs.mode == report`. - GH_AW_PROMPT_576e86da4707b909_EOF + GH_AW_PROMPT_2e88897e0e4c235c_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1042,9 +1070,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_d330cb482b71deae_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_db561e3d8fadb031_EOF' {"create_issue":{"labels":["agent:lint:python"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:python","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"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","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_d330cb482b71deae_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_db561e3d8fadb031_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1349,7 +1377,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_24eeb4b18f62076a_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_85539d4595f393bc_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -1390,7 +1418,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_24eeb4b18f62076a_EOF + GH_AW_MCP_CONFIG_85539d4595f393bc_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/chore-style-python.md b/.github/workflows/chore-style-python.md index 7908fad..669a84a 100644 --- a/.github/workflows/chore-style-python.md +++ b/.github/workflows/chore-style-python.md @@ -40,6 +40,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -86,6 +97,30 @@ Behavior summary: You are the Python style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Mode: report 1. `uv sync --frozen` to provision the environment. @@ -123,7 +158,9 @@ Apply dedup before emitting. even if ruff is clean; mypy has no `--fix` mode and unfixed type errors carry semantic risk. - `uv run pytest` — must exit 0. -5. Open one PR via `create-pull-request`: +5. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:python] auto-applied ruff format + lint fixes`. - Body: summary of files touched, count of format vs lint fixes, `Closes #` if a matching `agent:lint:python` issue is open. @@ -143,3 +180,5 @@ Apply dedup before emitting. - Do not auto-apply mypy fixes (mypy has no `--fix` mode; do not synthesize one). - Do not open more than one PR or issue per run. - Do not skip the pytest verification step in autofix mode. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/.github/workflows/chore-style-rust.lock.yml b/.github/workflows/chore-style-rust.lock.yml index 71fb337..cccdc97 100644 --- a/.github/workflows/chore-style-rust.lock.yml +++ b/.github/workflows/chore-style-rust.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f47408acef61f5eb61d21a1fa6f0328e8663d0e1e9d5a3765fad55f6e2f78e18","compiler_version":"v0.74.3","agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"bf051ff65cb21982680e647a4ea074f473f49a7554800af86f9db7f59f2bc75c","compiler_version":"v0.74.3","agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"Swatinem/rust-cache","sha":"v2","version":"v2"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"dtolnay/rust-toolchain","sha":"stable","version":"stable"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"},{"repo":"taiki-e/install-action","sha":"v2","version":"v2"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -238,23 +238,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_658becfb299e6630_EOF' + cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF' - GH_AW_PROMPT_658becfb299e6630_EOF + GH_AW_PROMPT_66d777413c34b61a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_658becfb299e6630_EOF' + cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF' Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_658becfb299e6630_EOF + GH_AW_PROMPT_66d777413c34b61a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_658becfb299e6630_EOF' + cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF' - GH_AW_PROMPT_658becfb299e6630_EOF + GH_AW_PROMPT_66d777413c34b61a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_658becfb299e6630_EOF' + cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -283,9 +283,9 @@ jobs: {{/if}} - GH_AW_PROMPT_658becfb299e6630_EOF + GH_AW_PROMPT_66d777413c34b61a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_658becfb299e6630_EOF' + cat << 'GH_AW_PROMPT_66d777413c34b61a_EOF' @@ -771,6 +771,30 @@ jobs: You are the Rust style agent. Read `inputs.mode` and act accordingly. + ## Mode → safe-output contract (READ FIRST) + + The safe-output tool you call MUST match `inputs.mode`. Picking the wrong + tool is the defect tracked in issue #30 and is the single most important + rule in this prompt. + + - `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. + - `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + + If you find yourself about to call `create_issue` while `inputs.mode == + autofix`, stop and re-read this section. + ## Inputs 1. Current working tree of the default branch. @@ -819,7 +843,9 @@ jobs: Any non-zero exit means do not open the PR; emit `report_incomplete` naming the failing step and stop. - 4. Open one PR via `create-pull-request`: + 4. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:rust] auto-applied rustfmt + clippy fixes`. - Body: a summary of the files touched and a count of fmt vs clippy fixes. Include `Closes #` if an open `agent:lint:rust` issue @@ -840,8 +866,10 @@ jobs: - Do not run `clippy --fix` without a subsequent verification gate. - Do not open more than one PR or issue per run. - Do not bypass the verification gate by stashing uncommitted changes. + - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. + - Do not call `create_pull_request` when `inputs.mode == report`. - GH_AW_PROMPT_658becfb299e6630_EOF + GH_AW_PROMPT_66d777413c34b61a_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1049,9 +1077,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f663d45145b79719_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ab35a225af76be7c_EOF' {"create_issue":{"labels":["agent:lint:rust"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:rust","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"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","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_f663d45145b79719_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_ab35a225af76be7c_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1356,7 +1384,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_4d6f5d2881c54af9_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_40bf4451983d884a_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -1397,7 +1425,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_4d6f5d2881c54af9_EOF + GH_AW_MCP_CONFIG_40bf4451983d884a_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/chore-style-rust.md b/.github/workflows/chore-style-rust.md index 1e68d62..aede3d3 100644 --- a/.github/workflows/chore-style-rust.md +++ b/.github/workflows/chore-style-rust.md @@ -40,6 +40,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -86,6 +97,30 @@ Behavior summary: You are the Rust style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Inputs 1. Current working tree of the default branch. @@ -134,7 +169,9 @@ emitting; if a matching open issue exists, emit `update-issue` instead. Any non-zero exit means do not open the PR; emit `report_incomplete` naming the failing step and stop. -4. Open one PR via `create-pull-request`: +4. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:rust] auto-applied rustfmt + clippy fixes`. - Body: a summary of the files touched and a count of fmt vs clippy fixes. Include `Closes #` if an open `agent:lint:rust` issue @@ -155,3 +192,5 @@ emitting; if a matching open issue exists, emit `update-issue` instead. - Do not run `clippy --fix` without a subsequent verification gate. - Do not open more than one PR or issue per run. - Do not bypass the verification gate by stashing uncommitted changes. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/.github/workflows/chore-style-toml.lock.yml b/.github/workflows/chore-style-toml.lock.yml index f665cbf..32d69d8 100644 --- a/.github/workflows/chore-style-toml.lock.yml +++ b/.github/workflows/chore-style-toml.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"664f75ec218887caad08d5b1d20e6fa55be1bac3eb0eb4eafa021312578d04e2","compiler_version":"v0.74.3","agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5a0b0aa066838511a0d5828fa16ab809f9e30e25bb4da6c9f8d7b90e7b2f534b","compiler_version":"v0.74.3","agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/checkout","sha":"v4","version":"v4"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8a9a52673cb8f97ebb43b344618af2d6f2c9ac7f","version":"v0.74.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -233,23 +233,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF' + cat << 'GH_AW_PROMPT_812133c986f8f367_EOF' - GH_AW_PROMPT_d4ed0db97c47a8a8_EOF + GH_AW_PROMPT_812133c986f8f367_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF' + cat << 'GH_AW_PROMPT_812133c986f8f367_EOF' Tools: create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_d4ed0db97c47a8a8_EOF + GH_AW_PROMPT_812133c986f8f367_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF' + cat << 'GH_AW_PROMPT_812133c986f8f367_EOF' - GH_AW_PROMPT_d4ed0db97c47a8a8_EOF + GH_AW_PROMPT_812133c986f8f367_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF' + cat << 'GH_AW_PROMPT_812133c986f8f367_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -278,9 +278,9 @@ jobs: {{/if}} - GH_AW_PROMPT_d4ed0db97c47a8a8_EOF + GH_AW_PROMPT_812133c986f8f367_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_d4ed0db97c47a8a8_EOF' + cat << 'GH_AW_PROMPT_812133c986f8f367_EOF' @@ -686,6 +686,30 @@ jobs: You are the TOML style agent. Read `inputs.mode` and act accordingly. + ## Mode → safe-output contract (READ FIRST) + + The safe-output tool you call MUST match `inputs.mode`. Picking the wrong + tool is the defect tracked in issue #30 and is the single most important + rule in this prompt. + + - `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. + - `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + + If you find yourself about to call `create_issue` while `inputs.mode == + autofix`, stop and re-read this section. + ## Scope Every `*.toml` file under the repo root, excluding: @@ -722,7 +746,9 @@ jobs: - `taplo fmt --check` — must exit 0. - `taplo lint` — must exit 0. If lint fails, do not open the PR (taplo lint has no `--fix` mode for schema violations). - 3. Open one PR via `create-pull-request`: + 3. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:toml] auto-applied taplo fmt`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:toml`, `agent:autofix`. @@ -740,8 +766,10 @@ jobs: - Do not modify `Cargo.toml` or any TOML inside a Rust workspace. - Do not auto-apply schema-violation fixes; only formatter changes. - Do not open more than one PR or issue per run. + - Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. + - Do not call `create_pull_request` when `inputs.mode == report`. - GH_AW_PROMPT_d4ed0db97c47a8a8_EOF + GH_AW_PROMPT_812133c986f8f367_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -946,9 +974,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_b6cd7e6c7251f1da_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4ee635edfe543b08_EOF' {"create_issue":{"labels":["agent:lint:toml"],"max":1},"create_pull_request":{"draft":"${{ false }}","labels":["agent:lint:toml","agent:autofix"],"max":1,"max_patch_files":100,"max_patch_size":1024,"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","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_b6cd7e6c7251f1da_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_4ee635edfe543b08_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1253,7 +1281,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d1848364507628af_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_443708422ec8604f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -1294,7 +1322,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_d1848364507628af_EOF + GH_AW_MCP_CONFIG_443708422ec8604f_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/.github/workflows/chore-style-toml.md b/.github/workflows/chore-style-toml.md index aba0d22..62187d3 100644 --- a/.github/workflows/chore-style-toml.md +++ b/.github/workflows/chore-style-toml.md @@ -39,6 +39,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -81,6 +92,30 @@ Behavior summary: You are the TOML style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Scope Every `*.toml` file under the repo root, excluding: @@ -117,7 +152,9 @@ Apply dedup before emitting. - `taplo fmt --check` — must exit 0. - `taplo lint` — must exit 0. If lint fails, do not open the PR (taplo lint has no `--fix` mode for schema violations). -3. Open one PR via `create-pull-request`: +3. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:toml] auto-applied taplo fmt`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:toml`, `agent:autofix`. @@ -135,3 +172,5 @@ Apply dedup before emitting. - Do not modify `Cargo.toml` or any TOML inside a Rust workspace. - Do not auto-apply schema-violation fixes; only formatter changes. - Do not open more than one PR or issue per run. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/workflows/chore-style-go.md b/workflows/chore-style-go.md index a1df259..d9dc8b3 100644 --- a/workflows/chore-style-go.md +++ b/workflows/chore-style-go.md @@ -40,6 +40,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -87,6 +98,30 @@ Behavior summary: You are the Go style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Mode: report 1. `gofmt -l .` — list files needing format (empty output means clean). @@ -124,7 +159,9 @@ Apply dedup before emitting. the PR**; staticcheck has no `--fix` mode and surfaces semantic issues. - `go build ./...` — must exit 0. - `go test ./...` — must exit 0. -4. Open one PR via `create-pull-request`: +4. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:go] auto-applied gofmt + goimports`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:go`, `agent:autofix`. @@ -142,3 +179,5 @@ Apply dedup before emitting. - Do not modify `go.mod` or `go.sum`. - Do not auto-apply go vet or staticcheck suggestions. - Do not open more than one PR or issue per run. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/workflows/chore-style-ncl.md b/workflows/chore-style-ncl.md index 4ecefaf..5c3a773 100644 --- a/workflows/chore-style-ncl.md +++ b/workflows/chore-style-ncl.md @@ -39,6 +39,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -81,6 +92,30 @@ Behavior summary: You are the Nickel style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Scope Every `*.ncl` file under the repo root, excluding paths in `.gitignore` and @@ -117,7 +152,9 @@ Apply dedup before emitting. - `nickel format --check` on each file — must exit 0. - `nickel typecheck` on each file — must exit 0. **If typecheck fails, do not open the PR**; type errors are reported, not auto-fixed. -3. Open one PR via `create-pull-request`: +3. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:ncl] auto-applied nickel format`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:ncl`, `agent:autofix`. @@ -135,3 +172,5 @@ Apply dedup before emitting. - Do not auto-apply typecheck fixes. - Do not open more than one PR or issue per run. - Do not modify files outside the in-scope set. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/workflows/chore-style-python.md b/workflows/chore-style-python.md index 7908fad..669a84a 100644 --- a/workflows/chore-style-python.md +++ b/workflows/chore-style-python.md @@ -40,6 +40,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -86,6 +97,30 @@ Behavior summary: You are the Python style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Mode: report 1. `uv sync --frozen` to provision the environment. @@ -123,7 +158,9 @@ Apply dedup before emitting. even if ruff is clean; mypy has no `--fix` mode and unfixed type errors carry semantic risk. - `uv run pytest` — must exit 0. -5. Open one PR via `create-pull-request`: +5. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:python] auto-applied ruff format + lint fixes`. - Body: summary of files touched, count of format vs lint fixes, `Closes #` if a matching `agent:lint:python` issue is open. @@ -143,3 +180,5 @@ Apply dedup before emitting. - Do not auto-apply mypy fixes (mypy has no `--fix` mode; do not synthesize one). - Do not open more than one PR or issue per run. - Do not skip the pytest verification step in autofix mode. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/workflows/chore-style-rust.md b/workflows/chore-style-rust.md index 1e68d62..aede3d3 100644 --- a/workflows/chore-style-rust.md +++ b/workflows/chore-style-rust.md @@ -40,6 +40,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -86,6 +97,30 @@ Behavior summary: You are the Rust style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Inputs 1. Current working tree of the default branch. @@ -134,7 +169,9 @@ emitting; if a matching open issue exists, emit `update-issue` instead. Any non-zero exit means do not open the PR; emit `report_incomplete` naming the failing step and stop. -4. Open one PR via `create-pull-request`: +4. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:rust] auto-applied rustfmt + clippy fixes`. - Body: a summary of the files touched and a count of fmt vs clippy fixes. Include `Closes #` if an open `agent:lint:rust` issue @@ -155,3 +192,5 @@ emitting; if a matching open issue exists, emit `update-issue` instead. - Do not run `clippy --fix` without a subsequent verification gate. - Do not open more than one PR or issue per run. - Do not bypass the verification gate by stashing uncommitted changes. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`. diff --git a/workflows/chore-style-toml.md b/workflows/chore-style-toml.md index aba0d22..62187d3 100644 --- a/workflows/chore-style-toml.md +++ b/workflows/chore-style-toml.md @@ -39,6 +39,17 @@ safe-outputs: private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: - ${{ github.event.repository.name }} + # Note (issue #30): both create-issue and create-pull-request are listed + # so the agent has the right tool available in either mode. The agent + # MUST select the tool that matches inputs.mode — see the prose contract + # below. Gating via templated `max` was attempted but rejected: every + # available gh-aw expression form for a ternary (`cond && '0' || '1'`, + # `fromJSON('{"k":"v"}')[inputs.x]`) renders into the safe-outputs JSON + # env var with characters that actionlint cannot lex (`&&` → `&&`; + # nested `\"` inside `fromJSON()`). The prose contract is the primary + # defense; if the agent calls create_issue in autofix anyway, the + # safe-output allowlist will still create the issue but the chore + # behaviour is wrong and visible from the run log. create-issue: max: 1 labels: @@ -81,6 +92,30 @@ Behavior summary: You are the TOML style agent. Read `inputs.mode` and act accordingly. +## Mode → safe-output contract (READ FIRST) + +The safe-output tool you call MUST match `inputs.mode`. Picking the wrong +tool is the defect tracked in issue #30 and is the single most important +rule in this prompt. + +- `mode == report`: + - You MUST call `create_issue` (or `update_issue` on dedup hit). + - You MUST NOT call `create_pull_request`. Report mode does not modify + files; opening a PR is a contract violation. +- `mode == autofix`: + - You MUST call `create_pull_request`. + - You MUST NOT call `create_issue` or `update_issue` under any + circumstances — not as a fallback, not to "also notify", not because + the verification gate failed. If the verification gate fails, emit + `report_incomplete` and stop; do not file a new issue. + - Even though `create_issue` appears in the safe-outputs allowlist + (so report mode can use it), calling it from autofix is a contract + violation tracked by issue #30. The wrong-tool behaviour is visible + in the run log and treated as a defect. + +If you find yourself about to call `create_issue` while `inputs.mode == +autofix`, stop and re-read this section. + ## Scope Every `*.toml` file under the repo root, excluding: @@ -117,7 +152,9 @@ Apply dedup before emitting. - `taplo fmt --check` — must exit 0. - `taplo lint` — must exit 0. If lint fails, do not open the PR (taplo lint has no `--fix` mode for schema violations). -3. Open one PR via `create-pull-request`: +3. Open one PR via `create-pull-request` (safe-output tool + `create_pull_request`). **Do not call `create_issue` in this mode** — + see the contract above. - Title: `[lint:toml] auto-applied taplo fmt`. - Body: summary of files touched, `Closes #` if applicable. - Labels: `agent:lint:toml`, `agent:autofix`. @@ -135,3 +172,5 @@ Apply dedup before emitting. - Do not modify `Cargo.toml` or any TOML inside a Rust workspace. - Do not auto-apply schema-violation fixes; only formatter changes. - Do not open more than one PR or issue per run. +- Do not call `create_issue` or `update_issue` when `inputs.mode == autofix`. +- Do not call `create_pull_request` when `inputs.mode == report`.