From d516fccec72396c7292258ead19627ca2c1224d0 Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Sun, 31 May 2026 07:32:24 +0200 Subject: [PATCH 1/8] ci: add gofmt pre-commit hook Enforce gofmt formatting via pre-commit so violations fail before commit. Placed before go-vet in the local hooks section. Ref #960 Co-Authored-By: Claude Opus 4.6 (1M context) Generated-by: Claude rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e98d5912..79754ce0b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,6 +77,13 @@ repos: - repo: local hooks: + - id: go-fmt + name: gofmt + entry: bash -c 'out=$(gofmt -l .); [ -z "$out" ] || { echo "$out"; exit 1; }' + language: system + types: [go] + pass_filenames: false + - id: go-vet name: go vet entry: go vet ./... From 71c41cb8d3f4fd16466f11a84af44cd76edc5648 Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Tue, 2 Jun 2026 09:08:45 +0200 Subject: [PATCH 2/8] ci: simplify gofmt hook to use native pre-commit file filtering Let pre-commit pass staged Go files to gofmt instead of scanning the entire tree. Uses gofmt -w to auto-fix formatting in place. Co-Authored-By: Claude Opus 4.6 (1M context) Generated-by: Claude rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- .pre-commit-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 79754ce0b..e501f216e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -79,10 +79,9 @@ repos: hooks: - id: go-fmt name: gofmt - entry: bash -c 'out=$(gofmt -l .); [ -z "$out" ] || { echo "$out"; exit 1; }' + entry: gofmt -w language: system types: [go] - pass_filenames: false - id: go-vet name: go vet From 05f034ece04ad78abcb2ee18fe433286d196d0c1 Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Sun, 31 May 2026 07:44:55 +0200 Subject: [PATCH 3/8] ci: add pre-commit hook to detect mint embed drift Add lint-mint-embed-sync hook that verifies main.go, go.mod, and go.sum in internal/mint/ match their .embed copies in internal/dispatch/gcf/mintsrc/. Runs only when either directory is touched. Co-Authored-By: Claude Opus 4.6 (1M context) Generated-by: Claude rh-pre-commit.version: 2.3.2 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- .pre-commit-config.yaml | 7 +++++++ hack/lint-mint-embed-sync | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100755 hack/lint-mint-embed-sync diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e501f216e..956ffa9b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -90,6 +90,13 @@ repos: types: [go] pass_filenames: false + - id: lint-mint-embed-sync + name: lint mint embed sync + entry: ./hack/lint-mint-embed-sync + language: script + files: ^(internal/mint/|internal/dispatch/gcf/mintsrc/) + pass_filenames: false + - id: lint-adr-status name: lint ADR statuses entry: ./hack/lint-adr-status diff --git a/hack/lint-mint-embed-sync b/hack/lint-mint-embed-sync new file mode 100755 index 000000000..aa1b80545 --- /dev/null +++ b/hack/lint-mint-embed-sync @@ -0,0 +1,26 @@ +#!/bin/bash + +# lint-mint-embed-sync - Verify that the mint Cloud Function source files +# (main.go, go.mod, go.sum) match their embedded copies used for deployment. +# See CLAUDE.md for details on the mint/embed sync requirement. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +SRC="$REPO_ROOT/internal/mint" +DST="$REPO_ROOT/internal/dispatch/gcf/mintsrc" + +rc=0 +for f in main.go go.mod go.sum; do + if ! diff -q "$SRC/$f" "$DST/$f.embed" >/dev/null 2>&1; then + echo "DESYNC: internal/mint/$f != internal/dispatch/gcf/mintsrc/$f.embed" >&2 + rc=1 + fi +done + +if [[ $rc -eq 0 ]]; then + echo "OK: mint embed files in sync" +fi +exit $rc From 64cbd315be9f76d38037a5386ecd1b9a3e109bb0 Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Wed, 3 Jun 2026 08:52:16 +0200 Subject: [PATCH 4/8] ci: use cmp instead of diff for mint embed sync check cmp -s is faster for byte-level equality checks where we don't need the actual diff output. Co-Authored-By: Claude Opus 4.6 (1M context) Generated-by: Claude rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- hack/lint-mint-embed-sync | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/lint-mint-embed-sync b/hack/lint-mint-embed-sync index aa1b80545..a5553bb9b 100755 --- a/hack/lint-mint-embed-sync +++ b/hack/lint-mint-embed-sync @@ -14,7 +14,7 @@ DST="$REPO_ROOT/internal/dispatch/gcf/mintsrc" rc=0 for f in main.go go.mod go.sum; do - if ! diff -q "$SRC/$f" "$DST/$f.embed" >/dev/null 2>&1; then + if ! cmp -s "$SRC/$f" "$DST/$f.embed"; then echo "DESYNC: internal/mint/$f != internal/dispatch/gcf/mintsrc/$f.embed" >&2 rc=1 fi From aec66a26d52ecb5ae5ca496701cb6f4b1518346c Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Thu, 4 Jun 2026 12:39:50 +0200 Subject: [PATCH 5/8] ci: dynamically discover mint files for embed sync check Instead of hardcoding the three files to check, glob all non-test files in internal/mint/ so newly added files are automatically covered. Co-Authored-By: Claude Opus 4.6 (1M context) Generated-by: Claude rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- hack/lint-mint-embed-sync | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/hack/lint-mint-embed-sync b/hack/lint-mint-embed-sync index a5553bb9b..c85c74da7 100755 --- a/hack/lint-mint-embed-sync +++ b/hack/lint-mint-embed-sync @@ -13,9 +13,13 @@ SRC="$REPO_ROOT/internal/mint" DST="$REPO_ROOT/internal/dispatch/gcf/mintsrc" rc=0 -for f in main.go go.mod go.sum; do - if ! cmp -s "$SRC/$f" "$DST/$f.embed"; then - echo "DESYNC: internal/mint/$f != internal/dispatch/gcf/mintsrc/$f.embed" >&2 +for f in "$SRC"/*; do + name="$(basename "$f")" + if [[ "$name" == *_test.go ]]; then + continue + fi + if ! cmp -s "$f" "$DST/$name.embed"; then + echo "DESYNC: internal/mint/$name != internal/dispatch/gcf/mintsrc/$name.embed" >&2 rc=1 fi done From b9f8919e501a928a7736d294e070846d6a041152 Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Fri, 5 Jun 2026 06:37:50 +0200 Subject: [PATCH 6/8] ci: Add exception for internal/mint/go.mod Since commit eecac658c there is intentional difference between internal/mint/go.mod and internal/dispatch/gcf/mintsrc/go.mod.embed with replace directive. rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- hack/lint-mint-embed-sync | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hack/lint-mint-embed-sync b/hack/lint-mint-embed-sync index c85c74da7..0aa97996c 100755 --- a/hack/lint-mint-embed-sync +++ b/hack/lint-mint-embed-sync @@ -18,6 +18,14 @@ for f in "$SRC"/*; do if [[ "$name" == *_test.go ]]; then continue fi + + # "go.mod" is a special case since commit eecac658c + if [[ "$name" == "go.mod" ]]; then + ff="$( mktemp )" + sed 's| => ../mintcore$| => ./mintcore|' "$f" >"$ff" + f="$ff" + fi + if ! cmp -s "$f" "$DST/$name.embed"; then echo "DESYNC: internal/mint/$name != internal/dispatch/gcf/mintsrc/$name.embed" >&2 rc=1 From 69dab8eeea9362bd9c6f02921a7a6469f83f48fe Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Fri, 5 Jun 2026 09:13:06 +0200 Subject: [PATCH 7/8] feat: Expand hack/lint-mint-embed-sync to cover additional dir rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- .pre-commit-config.yaml | 2 +- hack/lint-mint-embed-sync | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 956ffa9b6..8055192cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -94,7 +94,7 @@ repos: name: lint mint embed sync entry: ./hack/lint-mint-embed-sync language: script - files: ^(internal/mint/|internal/dispatch/gcf/mintsrc/) + files: ^(internal/mint/|internal/mintcore/|internal/dispatch/gcf/mintsrc/) pass_filenames: false - id: lint-adr-status diff --git a/hack/lint-mint-embed-sync b/hack/lint-mint-embed-sync index 0aa97996c..20642e0be 100755 --- a/hack/lint-mint-embed-sync +++ b/hack/lint-mint-embed-sync @@ -9,17 +9,18 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +rc=0 + SRC="$REPO_ROOT/internal/mint" DST="$REPO_ROOT/internal/dispatch/gcf/mintsrc" -rc=0 for f in "$SRC"/*; do name="$(basename "$f")" if [[ "$name" == *_test.go ]]; then continue fi - # "go.mod" is a special case since commit eecac658c + # "go.mod" special case since commit eecac658c if [[ "$name" == "go.mod" ]]; then ff="$( mktemp )" sed 's| => ../mintcore$| => ./mintcore|' "$f" >"$ff" @@ -30,6 +31,24 @@ for f in "$SRC"/*; do echo "DESYNC: internal/mint/$name != internal/dispatch/gcf/mintsrc/$name.embed" >&2 rc=1 fi + + # "go.mod" special case cleanup + [[ "$name" == "go.mod" ]] && rm "$f" +done + +SRC="$REPO_ROOT/internal/mintcore" +DST="$REPO_ROOT/internal/dispatch/gcf/mintsrc/mintcore" + +for f in "$SRC"/*; do + name="$(basename "$f")" + if [[ "$name" == *_test.go ]]; then + continue + fi + + if ! cmp -s "$f" "$DST/$name.embed"; then + echo "DESYNC: internal/mintcore/$name != internal/dispatch/gcf/mintsrc/mintcore/$name.embed" >&2 + rc=1 + fi done if [[ $rc -eq 0 ]]; then From 7f6d68bae192af63a50c289489454cd73f3ed73e Mon Sep 17 00:00:00 2001 From: Jan Hutar Date: Fri, 5 Jun 2026 06:40:44 +0200 Subject: [PATCH 8/8] style: apply gofmt to all Go source files rh-pre-commit.version: 2.4.0 rh-pre-commit.check-secrets: ENABLED Signed-off-by: Jan Hutar --- internal/cli/bootstrap_input.go | 8 +-- internal/cli/lock_test.go | 2 +- internal/dispatch/gcf/gcp_test.go | 2 +- .../gcf/mintsrc/mintcore/github.go.embed | 4 +- .../mintsrc/mintcore/jwks_verifier.go.embed | 12 ++--- internal/dispatch/gcf/provisioner.go | 14 ++--- internal/fetch/cache_test.go | 10 ++-- internal/forge/fake.go | 2 +- internal/forge/github/types.go | 2 +- internal/harness/compose_test.go | 10 ++-- internal/harness/forge_test.go | 4 +- internal/harness/harness.go | 54 +++++++++---------- internal/harness/integration_test.go | 12 ++--- internal/layers/inference_test.go | 10 ++-- internal/layers/secrets_test.go | 4 +- internal/layers/workflows_test.go | 2 - internal/lock/lock.go | 6 +-- internal/mintcore/github.go | 4 +- internal/mintcore/github_test.go | 12 +++-- internal/mintcore/handler_test.go | 2 +- internal/mintcore/jwks_verifier.go | 12 ++--- internal/resolve/resolve_test.go | 6 +-- internal/runtime/claude_transcript.go | 4 +- internal/runtime/claude_transcript_test.go | 6 +-- internal/security/ssrf.go | 8 +-- internal/sentencetoken/token.go | 4 +- 26 files changed, 110 insertions(+), 106 deletions(-) diff --git a/internal/cli/bootstrap_input.go b/internal/cli/bootstrap_input.go index dd3cd7e46..2d88daf21 100644 --- a/internal/cli/bootstrap_input.go +++ b/internal/cli/bootstrap_input.go @@ -18,10 +18,10 @@ type harnessBootstrapWithHooks struct { hooks security.ClaudeSandboxHooks } -func (b *harnessBootstrap) SandboxName() string { return b.sandboxName } -func (b *harnessBootstrap) AgentPath() string { return b.agentPath } -func (b *harnessBootstrap) SkillDirs() []string { return b.skillDirs } -func (b *harnessBootstrap) PluginDirs() []string { return b.pluginDirs } +func (b *harnessBootstrap) SandboxName() string { return b.sandboxName } +func (b *harnessBootstrap) AgentPath() string { return b.agentPath } +func (b *harnessBootstrap) SkillDirs() []string { return b.skillDirs } +func (b *harnessBootstrap) PluginDirs() []string { return b.pluginDirs } func (b *harnessBootstrapWithHooks) ClaudeSandboxHooks() security.ClaudeSandboxHooks { return b.hooks diff --git a/internal/cli/lock_test.go b/internal/cli/lock_test.go index e39302d26..975e3726c 100644 --- a/internal/cli/lock_test.go +++ b/internal/cli/lock_test.go @@ -119,7 +119,7 @@ func TestRunLock_SkillDirectoryType(t *testing.T) { skillMD := []byte("# Test skill\nA test skill.") helperSh := []byte("#!/bin/bash\necho hello") skillFiles := map[string][]byte{ - "SKILL.md": skillMD, + "SKILL.md": skillMD, "scripts/helper.sh": helperSh, } treeHash := fetch.ComputeTreeHash(skillFiles) diff --git a/internal/dispatch/gcf/gcp_test.go b/internal/dispatch/gcf/gcp_test.go index 3a28cb2d4..9552f4caf 100644 --- a/internal/dispatch/gcf/gcp_test.go +++ b/internal/dispatch/gcf/gcp_test.go @@ -1977,7 +1977,7 @@ func TestLiveGCFClient_GetServiceRevisionInfo_ShortRevisionName(t *testing.T) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ "template": map[string]interface{}{ - "revision": "my-svc-00042-abc", + "revision": "my-svc-00042-abc", "containers": []interface{}{map[string]interface{}{}}, }, "trafficStatuses": []interface{}{ diff --git a/internal/dispatch/gcf/mintsrc/mintcore/github.go.embed b/internal/dispatch/gcf/mintsrc/mintcore/github.go.embed index d2077a6d1..9844af361 100644 --- a/internal/dispatch/gcf/mintsrc/mintcore/github.go.embed +++ b/internal/dispatch/gcf/mintsrc/mintcore/github.go.embed @@ -31,8 +31,8 @@ type installationResponse struct { // installationTokenResponse is the response from POST /app/installations/{id}/access_tokens. type installationTokenResponse struct { - Token string `json:"token"` - ExpiresAt string `json:"expires_at"` + Token string `json:"token"` + ExpiresAt string `json:"expires_at"` Permissions map[string]string `json:"permissions,omitempty"` Repositories []installationTokenRepository `json:"repositories,omitempty"` RepositorySelection string `json:"repository_selection,omitempty"` diff --git a/internal/dispatch/gcf/mintsrc/mintcore/jwks_verifier.go.embed b/internal/dispatch/gcf/mintsrc/mintcore/jwks_verifier.go.embed index f5fad0afb..22e4f7318 100644 --- a/internal/dispatch/gcf/mintsrc/mintcore/jwks_verifier.go.embed +++ b/internal/dispatch/gcf/mintsrc/mintcore/jwks_verifier.go.embed @@ -36,12 +36,12 @@ type JWKSVerifier struct { allowedWorkflowFiles []string perRepoWIFRepos map[string]bool - mu sync.RWMutex - keys map[string]*rsa.PublicKey - cachedJWKSURI string - fetchedAt time.Time - lastKidMissAt time.Time - refreshGroup singleflight.Group + mu sync.RWMutex + keys map[string]*rsa.PublicKey + cachedJWKSURI string + fetchedAt time.Time + lastKidMissAt time.Time + refreshGroup singleflight.Group } // JWKSVerifierConfig configures a new JWKSVerifier. diff --git a/internal/dispatch/gcf/provisioner.go b/internal/dispatch/gcf/provisioner.go index 99cc2cbbe..381c1da1a 100644 --- a/internal/dispatch/gcf/provisioner.go +++ b/internal/dispatch/gcf/provisioner.go @@ -49,16 +49,16 @@ var embeddedMintSource embed.FS // triggering Go's module boundary detection) to their real names for the // Cloud Function deployment zip. var embeddedMintFiles = map[string]string{ - "go.mod.embed": "go.mod", - "go.sum.embed": "go.sum", - "main.go.embed": "main.go", - "mintcore/go.mod.embed": "mintcore/go.mod", - "mintcore/go.sum.embed": "mintcore/go.sum", - "mintcore/gcp_pem.go.embed": "mintcore/gcp_pem.go", + "go.mod.embed": "go.mod", + "go.sum.embed": "go.sum", + "main.go.embed": "main.go", + "mintcore/go.mod.embed": "mintcore/go.mod", + "mintcore/go.sum.embed": "mintcore/go.sum", + "mintcore/gcp_pem.go.embed": "mintcore/gcp_pem.go", "mintcore/github.go.embed": "mintcore/github.go", "mintcore/handler.go.embed": "mintcore/handler.go", "mintcore/interfaces.go.embed": "mintcore/interfaces.go", - "mintcore/jwks_verifier.go.embed": "mintcore/jwks_verifier.go", + "mintcore/jwks_verifier.go.embed": "mintcore/jwks_verifier.go", "mintcore/claims.go.embed": "mintcore/claims.go", "mintcore/patterns.go.embed": "mintcore/patterns.go", "mintcore/sts_verifier.go.embed": "mintcore/sts_verifier.go", diff --git a/internal/fetch/cache_test.go b/internal/fetch/cache_test.go index fb2ff12b8..17536a058 100644 --- a/internal/fetch/cache_test.go +++ b/internal/fetch/cache_test.go @@ -254,8 +254,8 @@ func TestCachePutDir_CacheGetDir_RoundTrip(t *testing.T) { root := t.TempDir() url := "https://github.com/example/repo/tree/main/skills/review" files := map[string][]byte{ - "SKILL.md": []byte("# Review Skill\nA skill for reviews."), - "scripts/helper.sh": []byte("#!/bin/bash\necho helper"), + "SKILL.md": []byte("# Review Skill\nA skill for reviews."), + "scripts/helper.sh": []byte("#!/bin/bash\necho helper"), "sub-agents/triage.md": []byte("# Triage sub-agent"), } @@ -322,9 +322,9 @@ func TestCacheGetDir_IntegrityVerification(t *testing.T) { func TestCachePutDir_NestedDirectories(t *testing.T) { root := t.TempDir() files := map[string][]byte{ - "SKILL.md": []byte("# Skill"), - "scripts/helper.sh": []byte("#!/bin/bash\necho hi"), - "sub-agents/review.md": []byte("# Review"), + "SKILL.md": []byte("# Skill"), + "scripts/helper.sh": []byte("#!/bin/bash\necho hi"), + "sub-agents/review.md": []byte("# Review"), "sub-agents/deep/nested/file.md": []byte("# Deep nested"), } diff --git a/internal/forge/fake.go b/internal/forge/fake.go index aec624109..69686d275 100644 --- a/internal/forge/fake.go +++ b/internal/forge/fake.go @@ -176,7 +176,7 @@ type FakeClient struct { CreatedReviews []ReviewRecord DismissedReviews []DismissedReviewRecord CommittedFiles []CommitFilesRecord - DeletedComments []int // comment IDs + DeletedComments []int // comment IDs // internal counters proposalCounter int diff --git a/internal/forge/github/types.go b/internal/forge/github/types.go index 25e1c38b9..6d0354935 100644 --- a/internal/forge/github/types.go +++ b/internal/forge/github/types.go @@ -61,7 +61,7 @@ func AgentAppConfig(org, role, appSet string) AppConfig { base.Name = fmt.Sprintf("%s-%s", appSet, role) - switch role{ + switch role { case "fullsend": base.Description = fmt.Sprintf("Fullsend orchestrator for %s", org) base.Permissions = AppPermissions{ diff --git a/internal/harness/compose_test.go b/internal/harness/compose_test.go index d60372f51..a1fa85b35 100644 --- a/internal/harness/compose_test.go +++ b/internal/harness/compose_test.go @@ -373,10 +373,10 @@ forge: require.NoError(t, err) // GitHub forge merged, then resolved - assert.Equal(t, "base-pre.sh", h.PreScript) // from base forge - assert.Equal(t, "child-post.sh", h.PostScript) // from child forge - assert.Contains(t, h.Skills, "gh-skill-base") // base skills - assert.Contains(t, h.Skills, "gh-skill-child") // child skills + assert.Equal(t, "base-pre.sh", h.PreScript) // from base forge + assert.Equal(t, "child-post.sh", h.PostScript) // from child forge + assert.Contains(t, h.Skills, "gh-skill-base") // base skills + assert.Contains(t, h.Skills, "gh-skill-child") // child skills assert.Equal(t, "base-value1", h.RunnerEnv["GH_KEY1"]) assert.Equal(t, "child-value2", h.RunnerEnv["GH_KEY2"]) @@ -805,7 +805,7 @@ func TestMergeForgeBlocks(t *testing.T) { // GitHub merged gh := result["github"] require.NotNil(t, gh) - assert.Equal(t, "base-pre.sh", gh.PreScript) // inherited + assert.Equal(t, "base-pre.sh", gh.PreScript) // inherited assert.Equal(t, "child-post.sh", gh.PostScript) // from child assert.Equal(t, []string{"base-skill", "child-skill"}, gh.Skills) assert.Equal(t, "base1", gh.RunnerEnv["KEY1"]) // inherited diff --git a/internal/harness/forge_test.go b/internal/harness/forge_test.go index b59727297..67c88e85b 100644 --- a/internal/harness/forge_test.go +++ b/internal/harness/forge_test.go @@ -95,8 +95,8 @@ func TestResolveForge_RunnerEnvMerge(t *testing.T) { Forge: map[string]*ForgeConfig{ "github": { RunnerEnv: map[string]string{ - "OVERRIDE": "forge_val", - "GH_TOKEN": "${GH_TOKEN}", + "OVERRIDE": "forge_val", + "GH_TOKEN": "${GH_TOKEN}", }, }, }, diff --git a/internal/harness/harness.go b/internal/harness/harness.go index 97f2fbfaa..ce6d90b20 100644 --- a/internal/harness/harness.go +++ b/internal/harness/harness.go @@ -19,9 +19,9 @@ var ( validModelName = regexp.MustCompile(`^[a-zA-Z0-9_.@-]+$`) validPluginName = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) // validRoleName mirrors mintcore.RolePattern — duplicated to avoid coupling harness→mintcore. - validRoleName = regexp.MustCompile(`^[a-z][a-z0-9_-]*$`) - validSlugName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]*$`) - envVarRef = regexp.MustCompile(`\$\{([^}]+)\}`) + validRoleName = regexp.MustCompile(`^[a-z][a-z0-9_-]*$`) + validSlugName = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]*$`) + envVarRef = regexp.MustCompile(`\$\{([^}]+)\}`) ) // HostFile describes a file on the host that must be copied into the sandbox @@ -124,8 +124,8 @@ type SandboxHooks struct { SecretRedactPostTool *bool `yaml:"secret_redact_posttool,omitempty"` // default: true UnicodePostTool *bool `yaml:"unicode_posttool,omitempty"` // default: true ContextSuppressPostTool *bool `yaml:"context_suppress_posttool,omitempty"` // default: true - CanaryPreTool *bool `yaml:"canary_pretool,omitempty"` // default: true - CanaryPostTool *bool `yaml:"canary_posttool,omitempty"` // default: true + CanaryPreTool *bool `yaml:"canary_pretool,omitempty"` // default: true + CanaryPostTool *bool `yaml:"canary_posttool,omitempty"` // default: true ToolAllowlistPreTool *ToolAllowlistConfig `yaml:"tool_allowlist_pretool,omitempty"` } @@ -195,29 +195,29 @@ type ValidationLoop struct { // Harness is the per-agent configuration that the runner reads to provision // a sandbox and launch one agent. It follows the ADR-0017 schema. type Harness struct { - Agent string `yaml:"agent"` - Doc string `yaml:"doc,omitempty"` // source-repo-only; not resolved at runtime, used by lint-agent-docs - Description string `yaml:"description,omitempty"` - Role string `yaml:"role,omitempty"` - Slug string `yaml:"slug,omitempty"` - Base string `yaml:"base,omitempty"` - Image string `yaml:"image,omitempty"` - Policy string `yaml:"policy,omitempty"` - Skills []string `yaml:"skills,omitempty"` - Plugins []string `yaml:"plugins,omitempty"` - Providers []string `yaml:"providers,omitempty"` - HostFiles []HostFile `yaml:"host_files,omitempty"` - APIServers []APIServer `yaml:"api_servers,omitempty"` - Model string `yaml:"model,omitempty"` - PreScript string `yaml:"pre_script,omitempty"` - PostScript string `yaml:"post_script,omitempty"` - AgentInput string `yaml:"agent_input,omitempty"` - ValidationLoop *ValidationLoop `yaml:"validation_loop,omitempty"` - RunnerEnv map[string]string `yaml:"runner_env,omitempty"` - TimeoutMinutes int `yaml:"timeout_minutes,omitempty"` - SandboxTimeoutSeconds int `yaml:"sandbox_timeout_seconds,omitempty"` + Agent string `yaml:"agent"` + Doc string `yaml:"doc,omitempty"` // source-repo-only; not resolved at runtime, used by lint-agent-docs + Description string `yaml:"description,omitempty"` + Role string `yaml:"role,omitempty"` + Slug string `yaml:"slug,omitempty"` + Base string `yaml:"base,omitempty"` + Image string `yaml:"image,omitempty"` + Policy string `yaml:"policy,omitempty"` + Skills []string `yaml:"skills,omitempty"` + Plugins []string `yaml:"plugins,omitempty"` + Providers []string `yaml:"providers,omitempty"` + HostFiles []HostFile `yaml:"host_files,omitempty"` + APIServers []APIServer `yaml:"api_servers,omitempty"` + Model string `yaml:"model,omitempty"` + PreScript string `yaml:"pre_script,omitempty"` + PostScript string `yaml:"post_script,omitempty"` + AgentInput string `yaml:"agent_input,omitempty"` + ValidationLoop *ValidationLoop `yaml:"validation_loop,omitempty"` + RunnerEnv map[string]string `yaml:"runner_env,omitempty"` + TimeoutMinutes int `yaml:"timeout_minutes,omitempty"` + SandboxTimeoutSeconds int `yaml:"sandbox_timeout_seconds,omitempty"` Security *SecurityConfig `yaml:"security,omitempty"` - AllowedRemoteResources []string `yaml:"allowed_remote_resources,omitempty"` + AllowedRemoteResources []string `yaml:"allowed_remote_resources,omitempty"` Forge map[string]*ForgeConfig `yaml:"forge,omitempty"` } diff --git a/internal/harness/integration_test.go b/internal/harness/integration_test.go index a96b101b1..0ccf90b1e 100644 --- a/internal/harness/integration_test.go +++ b/internal/harness/integration_test.go @@ -86,8 +86,8 @@ forge: // Skills = base skills + child skills + forge.github skills (concatenated in order). assert.Equal(t, []string{ "base-skill-1", "base-skill-2", // from base top-level - "child-skill-1", // from child top-level - "gh-forge-skill", // from forge.github + "child-skill-1", // from child top-level + "gh-forge-skill", // from forge.github }, h.Skills) // RunnerEnv = base env merged with forge env (forge keys win). @@ -146,10 +146,10 @@ forge: // then ResolveForge appends the merged forge skills to the already-concatenated // top-level skills (base-top + child-top). assert.Equal(t, []string{ - "base-s1", // base top-level - "child-s1", // child top-level - "base-forge-s1", // base forge.github (merged into child forge) - "child-forge-s1", // child forge.github + "base-s1", // base top-level + "child-s1", // child top-level + "base-forge-s1", // base forge.github (merged into child forge) + "child-forge-s1", // child forge.github }, h.Skills) } diff --git a/internal/layers/inference_test.go b/internal/layers/inference_test.go index dd14ed5fa..014813c65 100644 --- a/internal/layers/inference_test.go +++ b/internal/layers/inference_test.go @@ -23,10 +23,12 @@ type fakeProvider struct { err error } -func (f *fakeProvider) Name() string { return f.name } -func (f *fakeProvider) SecretNames() []string { return f.secretNames } -func (f *fakeProvider) Provision(_ context.Context) (map[string]string, error) { return f.secrets, f.err } -func (f *fakeProvider) Variables() map[string]string { return f.variables } +func (f *fakeProvider) Name() string { return f.name } +func (f *fakeProvider) SecretNames() []string { return f.secretNames } +func (f *fakeProvider) Provision(_ context.Context) (map[string]string, error) { + return f.secrets, f.err +} +func (f *fakeProvider) Variables() map[string]string { return f.variables } func newInferenceLayer(t *testing.T, client *forge.FakeClient, provider inference.Provider) (*InferenceLayer, *bytes.Buffer) { t.Helper() diff --git a/internal/layers/secrets_test.go b/internal/layers/secrets_test.go index f587cf11c..d8b9bac9a 100644 --- a/internal/layers/secrets_test.go +++ b/internal/layers/secrets_test.go @@ -27,12 +27,12 @@ func twoAgents() []AgentCredentials { { AgentEntry: config.AgentEntry{Role: "fullsend", Name: "FullsendBot", Slug: "fullsend-bot"}, PEM: "-----BEGIN RSA PRIVATE KEY-----\nfullsend-key\n-----END RSA PRIVATE KEY-----", - ClientID: "Iv1.abc111", + ClientID: "Iv1.abc111", }, { AgentEntry: config.AgentEntry{Role: "triage", Name: "TriageBot", Slug: "triage-bot"}, PEM: "-----BEGIN RSA PRIVATE KEY-----\ntriage-key\n-----END RSA PRIVATE KEY-----", - ClientID: "Iv1.abc222", + ClientID: "Iv1.abc222", }, } } diff --git a/internal/layers/workflows_test.go b/internal/layers/workflows_test.go index a6c0b1e88..5c769e6be 100644 --- a/internal/layers/workflows_test.go +++ b/internal/layers/workflows_test.go @@ -126,7 +126,6 @@ func TestWorkflowsLayer_Install_ManagedHeaders(t *testing.T) { } } - func TestWorkflowsLayer_Install_Error(t *testing.T) { client := &forge.FakeClient{ Repos: []forge.Repository{{ @@ -165,7 +164,6 @@ func TestWorkflowsLayer_Install_ExecutableModes(t *testing.T) { } } - func TestWorkflowsLayer_Uninstall_Noop(t *testing.T) { client := forge.NewFakeClient() layer, _ := newWorkflowsLayer(t, client) diff --git a/internal/lock/lock.go b/internal/lock/lock.go index 57f2705de..5244a6d59 100644 --- a/internal/lock/lock.go +++ b/internal/lock/lock.go @@ -18,7 +18,7 @@ import ( // LockFile is the top-level structure of .fullsend/lock.yaml. type LockFile struct { Version int `yaml:"version"` - GeneratedAt time.Time `yaml:"generated_at"` + GeneratedAt time.Time `yaml:"generated_at"` Harnesses map[string]HarnessLock `yaml:"harnesses"` } @@ -35,8 +35,8 @@ type DependencyEntry struct { Field string `yaml:"field"` URL string `yaml:"url"` SHA256 string `yaml:"sha256"` - Type string `yaml:"type,omitempty"` // "file" or "directory"; empty treated as "file" - Files []FileEntry `yaml:"files,omitempty"` // manifest of files (directory deps only) + Type string `yaml:"type,omitempty"` // "file" or "directory"; empty treated as "file" + Files []FileEntry `yaml:"files,omitempty"` // manifest of files (directory deps only) FetchedAt time.Time `yaml:"fetched_at"` TransitiveDeps []DependencyEntry `yaml:"transitive_deps,omitempty"` } diff --git a/internal/mintcore/github.go b/internal/mintcore/github.go index d2077a6d1..9844af361 100644 --- a/internal/mintcore/github.go +++ b/internal/mintcore/github.go @@ -31,8 +31,8 @@ type installationResponse struct { // installationTokenResponse is the response from POST /app/installations/{id}/access_tokens. type installationTokenResponse struct { - Token string `json:"token"` - ExpiresAt string `json:"expires_at"` + Token string `json:"token"` + ExpiresAt string `json:"expires_at"` Permissions map[string]string `json:"permissions,omitempty"` Repositories []installationTokenRepository `json:"repositories,omitempty"` RepositorySelection string `json:"repository_selection,omitempty"` diff --git a/internal/mintcore/github_test.go b/internal/mintcore/github_test.go index f98635755..81e79cc2f 100644 --- a/internal/mintcore/github_test.go +++ b/internal/mintcore/github_test.go @@ -47,8 +47,10 @@ func TestFindInstallation(t *testing.T) { assert.Equal(t, "/repos/myorg/my-repo/installation", r.URL.Path) assert.Contains(t, r.Header.Get("Authorization"), "Bearer ") json.NewEncoder(w).Encode(installationResponse{ - ID: 42, - Account: struct{ Login string `json:"login"` }{Login: "myorg"}, + ID: 42, + Account: struct { + Login string `json:"login"` + }{Login: "myorg"}, }) })) defer mockGH.Close() @@ -61,8 +63,10 @@ func TestFindInstallation(t *testing.T) { func TestFindInstallation_OrgMismatch(t *testing.T) { mockGH := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(installationResponse{ - ID: 42, - Account: struct{ Login string `json:"login"` }{Login: "other-org"}, + ID: 42, + Account: struct { + Login string `json:"login"` + }{Login: "other-org"}, }) })) defer mockGH.Close() diff --git a/internal/mintcore/handler_test.go b/internal/mintcore/handler_test.go index db9751575..a544aac20 100644 --- a/internal/mintcore/handler_test.go +++ b/internal/mintcore/handler_test.go @@ -15,8 +15,8 @@ import ( "io" "log" "net/http" - "os" "net/http/httptest" + "os" "strings" "testing" "time" diff --git a/internal/mintcore/jwks_verifier.go b/internal/mintcore/jwks_verifier.go index f5fad0afb..22e4f7318 100644 --- a/internal/mintcore/jwks_verifier.go +++ b/internal/mintcore/jwks_verifier.go @@ -36,12 +36,12 @@ type JWKSVerifier struct { allowedWorkflowFiles []string perRepoWIFRepos map[string]bool - mu sync.RWMutex - keys map[string]*rsa.PublicKey - cachedJWKSURI string - fetchedAt time.Time - lastKidMissAt time.Time - refreshGroup singleflight.Group + mu sync.RWMutex + keys map[string]*rsa.PublicKey + cachedJWKSURI string + fetchedAt time.Time + lastKidMissAt time.Time + refreshGroup singleflight.Group } // JWKSVerifierConfig configures a new JWKSVerifier. diff --git a/internal/resolve/resolve_test.go b/internal/resolve/resolve_test.go index e9ed2f105..36648466d 100644 --- a/internal/resolve/resolve_test.go +++ b/internal/resolve/resolve_test.go @@ -199,7 +199,7 @@ func TestResolveHarness_SkillDirFetchAndCache(t *testing.T) { fc := &forge.FakeClient{} treeHash := registerSkillDir(fc, "skills/review", map[string][]byte{ - "SKILL.md": skillMD, + "SKILL.md": skillMD, "scripts/helper.sh": helperSh, }) @@ -1073,7 +1073,7 @@ func TestResolveHarness_ZeroMaxDepthDisablesTransitive(t *testing.T) { MaxDepth: 0, // disabled }) require.NoError(t, err) - assert.Len(t, deps, 1) // only A + assert.Len(t, deps, 1) // only A assert.Len(t, h.Skills, 1) // only A } @@ -1166,7 +1166,7 @@ func TestResolveHarness_DirectAndTransitiveOverlap(t *testing.T) { MaxDepth: -1, }) require.NoError(t, err) - assert.Len(t, deps, 2) // A and B, each exactly once + assert.Len(t, deps, 2) // A and B, each exactly once assert.Len(t, h.Skills, 2) // A's path and B's path, B deduped // B must not appear twice in h.Skills. diff --git a/internal/runtime/claude_transcript.go b/internal/runtime/claude_transcript.go index 00e588671..bbc9eade0 100644 --- a/internal/runtime/claude_transcript.go +++ b/internal/runtime/claude_transcript.go @@ -98,9 +98,9 @@ func parseTranscriptFile(path string) (TranscriptError, bool) { return TranscriptError{ Source: filepath.Base(path), - IsError: lastResult.IsError, + IsError: lastResult.IsError, ErrorMessage: truncateError(lastResult.Result), - Subtype: lastResult.Subtype, + Subtype: lastResult.Subtype, }, true } diff --git a/internal/runtime/claude_transcript_test.go b/internal/runtime/claude_transcript_test.go index 8b074d61b..2be4bdd3f 100644 --- a/internal/runtime/claude_transcript_test.go +++ b/internal/runtime/claude_transcript_test.go @@ -222,9 +222,9 @@ func TestEmitTranscriptErrors(t *testing.T) { func TestEmitTranscriptErrors_EmptyMessage(t *testing.T) { summaries := []TranscriptError{ { - Source: "test.jsonl", - IsError: true, - Subtype: "error_unknown", + Source: "test.jsonl", + IsError: true, + Subtype: "error_unknown", }, } diff --git a/internal/security/ssrf.go b/internal/security/ssrf.go index 4904e3626..47024bb49 100644 --- a/internal/security/ssrf.go +++ b/internal/security/ssrf.go @@ -26,10 +26,10 @@ func NewSSRFValidator() *SSRFValidator { return &SSRFValidator{ blockedHosts: map[string]bool{ "metadata.google.internal": true, - "metadata.goog": true, - "169.254.169.254": true, - "100.100.100.200": true, - "fd00:ec2::254": true, + "metadata.goog": true, + "169.254.169.254": true, + "100.100.100.200": true, + "fd00:ec2::254": true, }, } } diff --git a/internal/sentencetoken/token.go b/internal/sentencetoken/token.go index 2f1c16955..e520b6ab1 100644 --- a/internal/sentencetoken/token.go +++ b/internal/sentencetoken/token.go @@ -148,8 +148,8 @@ func firstLower(t *token) bool { return unicode.IsLower([]rune(t.Tok)[0]) } -func isEllipsis(t *token) bool { return reEllipsis.MatchString(t.Tok) } -func isInitial(t *token) bool { return reInitial.MatchString(t.Tok) } +func isEllipsis(t *token) bool { return reEllipsis.MatchString(t.Tok) } +func isInitial(t *token) bool { return reInitial.MatchString(t.Tok) } func hasPeriodFinal(t *token) bool { return strings.HasSuffix(t.Tok, ".") } // hasSentEndChars — prose's customized version that excludes entities like "Yahoo!".