Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions internal/scaffold/fullsend-repo/harness/code.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ policy: policies/code.yaml
role: coder
slug: fullsend-ai-coder

pre_script: scripts/pre-code.sh
post_script: scripts/post-code.sh

host_files:
- src: env/gcp-vertex.env
dest: /sandbox/workspace/.env.d/gcp-vertex.env
Expand All @@ -33,20 +30,29 @@ host_files:
dest: /sandbox/workspace/.gcp-oidc-token
optional: true

pre_script: scripts/pre-code.sh
post_script: scripts/post-code.sh

skills:
- skills/code-implementation

plugins:
- plugins/gopls-lsp

# Environment variables available to post_script on the runner.
# Environment variables available to pre/post scripts on the runner.
# These are expanded from the runner environment and NEVER enter the sandbox.
runner_env:
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
ISSUE_NUMBER: "${ISSUE_NUMBER}"
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"
TARGET_BRANCH: "${TARGET_BRANCH}"

timeout_minutes: 35

forge:
github:
pre_script: scripts/pre-code.sh
post_script: scripts/post-code.sh
runner_env:
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
ISSUE_NUMBER: "${ISSUE_NUMBER}"
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"
19 changes: 12 additions & 7 deletions internal/scaffold/fullsend-repo/harness/fix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@ role: coder
slug: fullsend-ai-coder

pre_script: scripts/pre-fix.sh
post_script: scripts/post-fix.sh

validation_loop:
script: scripts/validate-output-schema.sh
max_iterations: 2

post_script: scripts/post-fix.sh

host_files:
- src: env/gcp-vertex.env
dest: /sandbox/workspace/.env.d/gcp-vertex.env
Expand All @@ -44,11 +43,6 @@ skills:
- skills/fix-review

runner_env:
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
PR_NUMBER: "${PR_NUMBER}"
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"
TARGET_BRANCH: "${TARGET_BRANCH}"
TRIGGER_SOURCE: "${TRIGGER_SOURCE}"
HUMAN_INSTRUCTION: "${HUMAN_INSTRUCTION}"
Expand All @@ -59,3 +53,14 @@ runner_env:
FULLSEND_OUTPUT_FILE: fix-result.json

timeout_minutes: 25

forge:
github:
pre_script: scripts/pre-fix.sh
post_script: scripts/post-fix.sh
runner_env:
PUSH_TOKEN: "${PUSH_TOKEN}"
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
PR_NUMBER: "${PR_NUMBER}"
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"
17 changes: 11 additions & 6 deletions internal/scaffold/fullsend-repo/harness/prioritize.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,23 @@ host_files:
expand: true

pre_script: scripts/pre-prioritize.sh
post_script: scripts/post-prioritize.sh

validation_loop:
script: scripts/validate-output-schema.sh
max_iterations: 2

post_script: scripts/post-prioritize.sh

runner_env:
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
GH_TOKEN: ${GH_TOKEN}
ORG: ${ORG}
PROJECT_NUMBER: ${PROJECT_NUMBER}
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/prioritize-result.schema.json

timeout_minutes: 10

forge:
github:
pre_script: scripts/pre-prioritize.sh
post_script: scripts/post-prioritize.sh
runner_env:
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
GH_TOKEN: ${GH_TOKEN}
ORG: ${ORG}
PROJECT_NUMBER: ${PROJECT_NUMBER}
15 changes: 10 additions & 5 deletions internal/scaffold/fullsend-repo/harness/retro.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,22 @@ skills:
- skills/agent-scaffolding

pre_script: scripts/pre-retro.sh
post_script: scripts/post-retro.sh

validation_loop:
script: scripts/validate-output-schema.sh
max_iterations: 2

post_script: scripts/post-retro.sh

runner_env:
ORIGINATING_URL: "${ORIGINATING_URL}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
GH_TOKEN: "${GH_TOKEN}"
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/retro-result.schema.json

timeout_minutes: 30

forge:
github:
pre_script: scripts/pre-retro.sh
post_script: scripts/post-retro.sh
runner_env:
ORIGINATING_URL: "${ORIGINATING_URL}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
GH_TOKEN: "${GH_TOKEN}"
21 changes: 13 additions & 8 deletions internal/scaffold/fullsend-repo/harness/review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ skills:
- skills/code-review
- skills/docs-review

pre_script: scripts/pre-review.sh

host_files:
- src: env/gcp-vertex.env
dest: /sandbox/workspace/.env.d/gcp-vertex.env
Expand All @@ -31,17 +29,24 @@ host_files:
dest: /sandbox/workspace/prior-review.txt
optional: true

pre_script: scripts/pre-review.sh
post_script: scripts/post-review.sh

validation_loop:
script: scripts/validate-output-schema.sh
max_iterations: 2

post_script: scripts/post-review.sh

runner_env:
REVIEW_TOKEN: "${REVIEW_TOKEN}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
PR_NUMBER: "${PR_NUMBER}"
GITHUB_PR_URL: "${GITHUB_PR_URL}"
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/review-result.schema.json

timeout_minutes: 20

forge:
github:
pre_script: scripts/pre-review.sh
post_script: scripts/post-review.sh
runner_env:
REVIEW_TOKEN: "${REVIEW_TOKEN}"
REPO_FULL_NAME: "${REPO_FULL_NAME}"
PR_NUMBER: "${PR_NUMBER}"
GITHUB_PR_URL: "${GITHUB_PR_URL}"
13 changes: 9 additions & 4 deletions internal/scaffold/fullsend-repo/harness/triage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,21 @@ skills:
- skills/issue-labels

pre_script: scripts/pre-triage.sh
post_script: scripts/post-triage.sh

validation_loop:
script: scripts/validate-output-schema.sh
max_iterations: 2

post_script: scripts/post-triage.sh

runner_env:
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
GH_TOKEN: ${GH_TOKEN}
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/triage-result.schema.json

timeout_minutes: 10

forge:
github:
pre_script: scripts/pre-triage.sh
post_script: scripts/post-triage.sh
runner_env:
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
GH_TOKEN: ${GH_TOKEN}
102 changes: 96 additions & 6 deletions internal/scaffold/scaffold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,20 +582,110 @@ func TestHarnessesLoadAndValidate(t *testing.T) {
}
t.Run(e.Name(), func(t *testing.T) {
harnessPath := filepath.Join(dir, "harness", e.Name())
h, err := harness.Load(harnessPath)
require.NoError(t, err, "Load should succeed")

err = h.ResolveRelativeTo(dir)
require.NoError(t, err, "ResolveRelativeTo should succeed")
t.Run("Load", func(t *testing.T) {
h, loadErr := harness.Load(harnessPath)
require.NoError(t, loadErr, "Load should succeed")

err = h.ValidateFilesExist()
require.NoError(t, err, "ValidateFilesExist should succeed")
// Top-level pre/post scripts serve as defaults even
// without forge resolution (local dev without --forge).
assert.NotEmpty(t, h.PreScript, "PreScript should be set at top level as default")
assert.NotEmpty(t, h.PostScript, "PostScript should be set at top level as default")
assert.NotNil(t, h.Forge, "Forge map should be present")
assert.Contains(t, h.Forge, "github", "Forge should have a github key")

resolveErr := h.ResolveRelativeTo(dir)
require.NoError(t, resolveErr, "ResolveRelativeTo should succeed")

existErr := h.ValidateFilesExist()
require.NoError(t, existErr, "ValidateFilesExist should succeed")
})

t.Run("LoadWithOpts_github", func(t *testing.T) {
h, loadErr := harness.LoadWithOpts(harnessPath, harness.LoadOpts{ForgePlatform: "github"})
require.NoError(t, loadErr, "LoadWithOpts should succeed")

assert.Nil(t, h.Forge, "Forge should be nil after resolution")
assert.NotEmpty(t, h.PreScript, "PreScript should be set after forge resolution")
assert.NotEmpty(t, h.PostScript, "PostScript should be set after forge resolution")
assert.NotEmpty(t, h.RunnerEnv, "RunnerEnv should be non-empty after merge")

resolveErr := h.ResolveRelativeTo(dir)
require.NoError(t, resolveErr, "ResolveRelativeTo should succeed")

existErr := h.ValidateFilesExist()
require.NoError(t, existErr, "ValidateFilesExist should succeed")
})
})
loaded++
}
assert.True(t, loaded >= 2, "expected at least 2 harnesses, got %d", loaded)
}

func TestHarnessForgeRunnerEnvMerge(t *testing.T) {
dir := t.TempDir()
err := WalkFullsendRepoAll(func(path string, content []byte) error {
dest := filepath.Join(dir, path)
if mkErr := os.MkdirAll(filepath.Dir(dest), 0o755); mkErr != nil {
return mkErr
}
return os.WriteFile(dest, content, 0o644)
})
require.NoError(t, err, "extracting scaffold")

tests := []struct {
file string
topLevelKeys []string
forgeGithubKeys []string
}{
{
file: "triage.yaml",
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
forgeGithubKeys: []string{"GITHUB_ISSUE_URL", "GH_TOKEN"},
},
{
file: "code.yaml",
topLevelKeys: []string{"TARGET_BRANCH"},
forgeGithubKeys: []string{"PUSH_TOKEN", "PUSH_TOKEN_SOURCE", "REPO_FULL_NAME", "ISSUE_NUMBER", "REPO_DIR"},
},
{
file: "review.yaml",
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
forgeGithubKeys: []string{"REVIEW_TOKEN", "REPO_FULL_NAME", "PR_NUMBER", "GITHUB_PR_URL"},
},
{
file: "fix.yaml",
topLevelKeys: []string{"TARGET_BRANCH", "TRIGGER_SOURCE", "HUMAN_INSTRUCTION", "FIX_ITERATION", "REVIEW_BODY_FILE", "PRE_AGENT_HEAD", "FULLSEND_OUTPUT_SCHEMA", "FULLSEND_OUTPUT_FILE"},
forgeGithubKeys: []string{"PUSH_TOKEN", "PUSH_TOKEN_SOURCE", "REPO_FULL_NAME", "PR_NUMBER", "REPO_DIR"},
},
{
file: "retro.yaml",
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
forgeGithubKeys: []string{"ORIGINATING_URL", "REPO_FULL_NAME", "GH_TOKEN"},
},
{
file: "prioritize.yaml",
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
forgeGithubKeys: []string{"GITHUB_ISSUE_URL", "GH_TOKEN", "ORG", "PROJECT_NUMBER"},
},
}

for _, tt := range tests {
t.Run(tt.file, func(t *testing.T) {
harnessPath := filepath.Join(dir, "harness", tt.file)
h, loadErr := harness.LoadWithOpts(harnessPath, harness.LoadOpts{ForgePlatform: "github"})
require.NoError(t, loadErr)

for _, key := range tt.topLevelKeys {
assert.Contains(t, h.RunnerEnv, key, "merged RunnerEnv should contain top-level key %s", key)
}
for _, key := range tt.forgeGithubKeys {
assert.Contains(t, h.RunnerEnv, key, "merged RunnerEnv should contain forge.github key %s", key)
}
})
}
}

func TestRepoMaintenanceWorkflowContent(t *testing.T) {
content, err := FullsendRepoFile(".github/workflows/repo-maintenance.yml")
require.NoError(t, err)
Expand Down
Loading