diff --git a/.github/workflows/purelock.lock.yml b/.github/workflows/purelock.lock.yml index df0496754e7..34b87850211 100644 --- a/.github/workflows/purelock.lock.yml +++ b/.github/workflows/purelock.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"3ed78504e1339af428a299f5977c729b72cb12b3b725f89d24007225649b7b6c","body_hash":"1422bc6dc023634cade0e76ad581b6c9cc63d488446142404825cff7263583ce","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.78"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2f52262b1ba02e2b58a3f18abd3113e725d0028c1a3a53056aeb2aaf49aec95f","body_hash":"aada0c044832ab9b0121bf9a35f95212cfae6b860a1df953ce31e46bd5d5ae21","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.78"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_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"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"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":"ghcr.io/github/serena-mcp-server:sha-891c160","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:sha-891c160@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"}]} # 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 # @@ -23,7 +23,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Daily workflow that locks down one uncovered pure Go function with a maximum-coverage, parallel-safe testify test suite +# Daily workflow that locks down up to 3 uncovered pure Go functions per run using parallel test-writer sub-agents # # Resolved workflow manifest: # Imports: @@ -604,9 +604,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_1c17c3b005e0e3db_EOF' - {"create_pull_request":{"allowed_files":["**/*_test.go","**/testdata/fuzz/**"],"draft":true,"expires":120,"if_no_changes":"ignore","labels":["automation","testing","coverage"],"max":1,"max_patch_files":4,"max_patch_size":4096,"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"],"protected_files_policy":"blocked","title_prefix":"[purelock] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_1c17c3b005e0e3db_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_efeda196c415ae47_EOF' + {"create_pull_request":{"allowed_files":["**/*_test.go","**/testdata/fuzz/**"],"draft":true,"expires":120,"if_no_changes":"ignore","labels":["automation","testing","coverage"],"max":1,"max_patch_files":8,"max_patch_size":4096,"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"],"protected_files_policy":"blocked","title_prefix":"[purelock] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_efeda196c415ae47_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1543,7 +1543,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "PureLock" - WORKFLOW_DESCRIPTION: "Daily workflow that locks down one uncovered pure Go function with a maximum-coverage, parallel-safe testify test suite" + WORKFLOW_DESCRIPTION: "Daily workflow that locks down up to 3 uncovered pure Go functions per run using parallel test-writer sub-agents" HAS_PATCH: ${{ needs.agent.outputs.has_patch }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: @@ -2294,7 +2294,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.grafana.net,*.sentry.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,go.dev,golang.org,goproxy.io,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkg.go.dev,ppa.launchpad.net,proxy.golang.org,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,storage.googleapis.com,sum.golang.org,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"allowed_files\":[\"**/*_test.go\",\"**/testdata/fuzz/**\"],\"draft\":true,\"expires\":120,\"if_no_changes\":\"ignore\",\"labels\":[\"automation\",\"testing\",\"coverage\"],\"max\":1,\"max_patch_files\":4,\"max_patch_size\":4096,\"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\"],\"protected_files_policy\":\"blocked\",\"title_prefix\":\"[purelock] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"allowed_files\":[\"**/*_test.go\",\"**/testdata/fuzz/**\"],\"draft\":true,\"expires\":120,\"if_no_changes\":\"ignore\",\"labels\":[\"automation\",\"testing\",\"coverage\"],\"max\":1,\"max_patch_files\":8,\"max_patch_size\":4096,\"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\"],\"protected_files_policy\":\"blocked\",\"title_prefix\":\"[purelock] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/purelock.md b/.github/workflows/purelock.md index 88d290c26a1..a39882af23d 100644 --- a/.github/workflows/purelock.md +++ b/.github/workflows/purelock.md @@ -2,7 +2,7 @@ private: true emoji: "🔐" name: PureLock -description: Daily workflow that locks down one uncovered pure Go function with a maximum-coverage, parallel-safe testify test suite +description: Daily workflow that locks down up to 3 uncovered pure Go functions per run using parallel test-writer sub-agents on: schedule: daily workflow_dispatch: @@ -149,7 +149,7 @@ safe-outputs: allowed-files: - "**/*_test.go" - "**/testdata/fuzz/**" - max-patch-files: 4 + max-patch-files: 8 noop: sandbox: agent: @@ -165,7 +165,7 @@ evals: # PureLock 🔐 -Lock down exactly **one** pure Go function per run with the smallest possible test suite that reaches the highest possible coverage. +Lock down up to **3** pure Go functions per run. The orchestrator selects candidates, fans out to parallel `test-writer` sub-agents for test generation, then merges results into one draft PR. The `purelock_precompute` job already did every expensive, deterministic step: it merged coverage profiles, type-checked `./pkg/...` with `go/packages`, ran a fixed-point side-effect analysis, and ranked the pure functions where coverage is weakest. Spend your budget writing tests, not exploring the repository. @@ -178,34 +178,110 @@ The `purelock_precompute` job already did every expensive, deterministic step: i Treat `candidates.json` as the **complete** working set. Do not scan the repository for other functions. -## 1. Select one function +## 1. Select up to 3 functions 1. Read `/tmp/gh-aw/cache-memory/purelock/state.json` when it exists. Shape: `{"processed":[{"key":"pkg/x/y.go:120:FuncName","date":"YYYY-MM-DD","outcome":"pr|noop"}]}`. -2. Walk `candidates.json` in order (already sorted by score) and pick the first candidate whose `key` (`file:line:name`) is absent from `processed`, or was processed more than 60 days ago. +2. Walk `candidates.json` in order (already sorted by score) and pick the first **up to 3** candidates whose `key` (`file:line:name`) is absent from `processed`, or was processed more than 60 days ago. 3. If every candidate was processed recently, call `noop` explaining that the current candidate set is exhausted, and still update cache memory. -4. Work on that single function only. -## 2. Confirm purity before writing tests +For each selected candidate, write its JSON entry to `/tmp/gh-aw/agent/purelock-/input.json` (creating the directory first). -The static analysis is conservative but not infallible. Confirm the selection with Serena before trusting it: +## 2. Generate tests in parallel -- Read the function body and verify it has no I/O, no clock or randomness, no global reads or writes, and no mutation of its arguments. -- Use `find_referencing_symbols` to see how callers use it — real call sites are the best source of realistic inputs and edge cases. -- If the function turns out to be impure, record it in cache memory with `"outcome":"noop"`, then move to the next candidate in the list (at most three attempts per run). +Invoke the `test-writer` sub-agent **simultaneously** for every selected candidate — start all invocations at once without waiting for any to finish first. -## 3. Measure the baseline +Pass each agent the path to its input file: -Run the package suite once to establish the baseline: +``` +Use the `test-writer` agent for the function described in /tmp/gh-aw/agent/purelock-/input.json. +Work dir: /tmp/gh-aw/agent/purelock-/ +``` + +Each agent writes `/tmp/gh-aw/agent/purelock-/result.json`: + +```json +{ + "key": "pkg/x/y.go:120:FuncName", + "outcome": "pr|noop", + "test_file": "", + "coverage_before": 42.5, + "coverage_after": 87.3, + "pkg_coverage_before": 61.0, + "pkg_coverage_after": 63.2, + "test_count": 1, + "subtest_count": 8, + "assertion_count": 16, + "fuzz_used": false, + "residual_uncovered": "", + "reason": "" +} +``` + +## 3. Collect and validate results + +After all agents finish, read every `result.json`. Separate results into `succeeded` (outcome=pr) and `failed` (outcome=noop). + +For each failed entry record it in cache memory with `"outcome":"noop"`. If **all** agents reported noop, call `noop` explaining the reasons, update cache memory, and stop. + +For each succeeded entry verify: + +1. `gofmt -l ` reports nothing. +2. `go vet .//` passes. +3. `go test .// -race -count=1` passes. +4. `coverage_after > coverage_before` for both the function and the package. + +Drop any entry that fails validation and record it as noop. If no entries remain, call `noop`, update cache, and stop. + +## 4. Create PR and update cache + +Create one draft pull request. Title: `[purelock] Lock down , , … with pure-function test suites` (list all succeeded function names). In the body include a section per function: + +- function, file, and signature +- why it is pure, quoting `purity_notes` and Serena verification from the sub-agent result +- coverage before and after for the function and the package +- test count, subtest count, and assertion count +- whether fuzzing was needed, and any residual uncovered lines + +Always — on pull request, noop, or exhausted list — write `/tmp/gh-aw/cache-memory/purelock/state.json` with **all** processed entries (both pr and noop) appended, deduplicated by `key`, keeping the newest date. This is what cycles the workflow through every pure function in the repository. + +## agent: `test-writer` + +--- +description: Confirms purity and writes a maximum-coverage testify suite for a single pure Go function +model: large +--- + +You are a focused test writer for a single Go function. Your only job is to generate and validate the test suite for the function described in your input file, then write the result. + +### Setup + +Read the input JSON from the path provided in your invocation message. It contains the full candidate entry: `package`, `file`, `line`, `name`, `receiver`, `signature`, `complexity`, `coverage_pct`, `has_test_file`, `fuzz_friendly`, `score`, `purity_notes`. + +Set `WORK_DIR` to the work dir path also provided in your invocation message. + +Activate the Serena project: + +```bash +serena activate_project /home/runner/work/gh-aw/gh-aw +``` + +### A. Confirm purity + +Read the function body with Serena and verify it has no I/O, no clock or randomness, no global reads or writes, and no mutation of its arguments. Use `find_referencing_symbols` to find real call sites — they are the best source of realistic inputs and edge cases. + +If the function turns out to be impure, write `result.json` with `"outcome":"noop"` and a descriptive `reason`, then stop. + +### B. Measure baseline ```bash -go test .// -count=1 -covermode=atomic -coverprofile=/tmp/purelock/before.out -go tool cover -func=/tmp/purelock/before.out | grep '::' +go test .// -count=1 -covermode=atomic -coverprofile="$WORK_DIR/before.out" +go tool cover -func="$WORK_DIR/before.out" | grep '::' ``` -Record both the target function coverage and the package total. These two numbers are the acceptance gate. +Record `coverage_before` (target function) and `pkg_coverage_before` (package total). -## 4. Write a maximum-coverage suite +### C. Write a maximum-coverage suite Optimize for **high coverage and high assertion density with the fewest possible tests**. Prefer one table-driven test over many small ones. @@ -219,7 +295,7 @@ Requirements: - Deterministic only: no sleeps, no clock, no network, no filesystem, no shared mutable globals. - Do not modify production code, existing tests, or unrelated files. -### Testify lint rules +Testify lint rules: - Never use `assert.True(t, a == b)` — use `assert.Equal`. - Never use `assert.Nil` for errors — use `require.NoError` or `assert.NoError`. @@ -227,45 +303,36 @@ Requirements: - Never ignore a returned error in a test. - Always give subtests descriptive names that state the behavior, not the input. -## 5. Escalate to fuzzing when coverage stalls +### D. Escalate to fuzzing when coverage stalls Re-measure after writing the table test: ```bash -go test .// -count=1 -covermode=atomic -coverprofile=/tmp/purelock/after.out -go tool cover -func=/tmp/purelock/after.out | grep '::' +go test .// -count=1 -covermode=atomic -coverprofile="$WORK_DIR/after.out" +go tool cover -func="$WORK_DIR/after.out" | grep '::' ``` -If the target function is still below 100% **and** the candidate has `"fuzz_friendly": true`, add a `Fuzz` target in the same file: +If the target function is still below 100% **and** `fuzz_friendly` is true, add a `Fuzz` target in the same file: - Seed the corpus with `f.Add(...)` using the table cases plus the uncovered edge inputs. -- Assert invariants that must hold for every input (no panic, idempotence, round-trip, or an output-range property), not specific values. -- Verify with `go test .// -run '^$' -fuzz 'Fuzz' -fuzztime=30s` and remove the fuzz target if it cannot close the gap. -- Commit any minimized corpus files the run produces under `testdata/fuzz/`. +- Assert invariants (no panic, idempotence, round-trip, or output-range property), not specific values. +- Verify with `go test .// -run '^$' -fuzz 'Fuzz' -fuzztime=30s` and remove the target if it cannot close the gap. +- Commit any minimized corpus files under `testdata/fuzz/`. -If the function is not fuzz friendly and coverage is still short, extend the table instead; document the residual uncovered lines in the pull request body. +If not fuzz friendly and coverage is still short, extend the table instead; document the residual uncovered lines in `residual_uncovered`. -## 6. Validate before shipping +### E. Validate All of these must pass: -1. `gofmt -l ` reports nothing. +1. `gofmt -l ` reports nothing. 2. `go vet .//`. 3. `go test .// -race -count=1`. -4. Target function coverage strictly increased versus the baseline. -5. Package total coverage strictly increased versus the baseline. -6. `git diff --name-only` lists only `*_test.go` files and `testdata/fuzz/**`. +4. `coverage_after > coverage_before` for both function and package. +5. `git diff --name-only` lists only `*_test.go` files and `testdata/fuzz/**`. -If any check fails, revert your edits and call `noop` with the reason. +If any check fails, revert the test file and write `result.json` with `"outcome":"noop"` and the failure reason. -## 7. Report and remember - -On success, create one draft pull request titled `Lock down with a pure-function test suite`. In the body include: - -- the function, file, and signature -- why it is pure, quoting the analyzer's `purity_notes` and your Serena verification -- coverage before and after, for the function and the package -- test count, subtest count, and assertion count -- whether fuzzing was needed, and any residual uncovered lines +### F. Write result -Always — on pull request, noop, or exhausted list — write `/tmp/gh-aw/cache-memory/purelock/state.json` with the processed entry appended, deduplicated by `key`, keeping the newest date. This is what cycles the workflow through every pure function in the repository. +Write `"$WORK_DIR/result.json"` with all fields populated. Set `"outcome":"pr"` on success, `"outcome":"noop"` on any failure.