Skip to content

Commit cc4b5b6

Browse files
authored
Merge pull request #2260 from fullsend-ai/feat/adr-0045-phase2-pr2-forge-github
feat(harness): move GitHub-specific fields into forge.github blocks
2 parents b876ef8 + 3483fbc commit cc4b5b6

7 files changed

Lines changed: 166 additions & 45 deletions

File tree

internal/scaffold/fullsend-repo/harness/code.yaml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ policy: policies/code.yaml
1717
role: coder
1818
slug: fullsend-ai-coder
1919

20-
pre_script: scripts/pre-code.sh
21-
post_script: scripts/post-code.sh
22-
2320
host_files:
2421
- src: env/gcp-vertex.env
2522
dest: /sandbox/workspace/.env.d/gcp-vertex.env
@@ -33,20 +30,29 @@ host_files:
3330
dest: /sandbox/workspace/.gcp-oidc-token
3431
optional: true
3532

33+
pre_script: scripts/pre-code.sh
34+
post_script: scripts/post-code.sh
35+
3636
skills:
3737
- skills/code-implementation
3838

3939
plugins:
4040
- plugins/gopls-lsp
4141

42-
# Environment variables available to post_script on the runner.
42+
# Environment variables available to pre/post scripts on the runner.
4343
# These are expanded from the runner environment and NEVER enter the sandbox.
4444
runner_env:
45-
PUSH_TOKEN: "${PUSH_TOKEN}"
46-
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
47-
REPO_FULL_NAME: "${REPO_FULL_NAME}"
48-
ISSUE_NUMBER: "${ISSUE_NUMBER}"
49-
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"
5045
TARGET_BRANCH: "${TARGET_BRANCH}"
5146

5247
timeout_minutes: 35
48+
49+
forge:
50+
github:
51+
pre_script: scripts/pre-code.sh
52+
post_script: scripts/post-code.sh
53+
runner_env:
54+
PUSH_TOKEN: "${PUSH_TOKEN}"
55+
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
56+
REPO_FULL_NAME: "${REPO_FULL_NAME}"
57+
ISSUE_NUMBER: "${ISSUE_NUMBER}"
58+
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"

internal/scaffold/fullsend-repo/harness/fix.yaml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ role: coder
1818
slug: fullsend-ai-coder
1919

2020
pre_script: scripts/pre-fix.sh
21+
post_script: scripts/post-fix.sh
2122

2223
validation_loop:
2324
script: scripts/validate-output-schema.sh
2425
max_iterations: 2
2526

26-
post_script: scripts/post-fix.sh
27-
2827
host_files:
2928
- src: env/gcp-vertex.env
3029
dest: /sandbox/workspace/.env.d/gcp-vertex.env
@@ -44,11 +43,6 @@ skills:
4443
- skills/fix-review
4544

4645
runner_env:
47-
PUSH_TOKEN: "${PUSH_TOKEN}"
48-
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
49-
REPO_FULL_NAME: "${REPO_FULL_NAME}"
50-
PR_NUMBER: "${PR_NUMBER}"
51-
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"
5246
TARGET_BRANCH: "${TARGET_BRANCH}"
5347
TRIGGER_SOURCE: "${TRIGGER_SOURCE}"
5448
HUMAN_INSTRUCTION: "${HUMAN_INSTRUCTION}"
@@ -59,3 +53,14 @@ runner_env:
5953
FULLSEND_OUTPUT_FILE: fix-result.json
6054

6155
timeout_minutes: 25
56+
57+
forge:
58+
github:
59+
pre_script: scripts/pre-fix.sh
60+
post_script: scripts/post-fix.sh
61+
runner_env:
62+
PUSH_TOKEN: "${PUSH_TOKEN}"
63+
PUSH_TOKEN_SOURCE: "${PUSH_TOKEN_SOURCE}"
64+
REPO_FULL_NAME: "${REPO_FULL_NAME}"
65+
PR_NUMBER: "${PR_NUMBER}"
66+
REPO_DIR: "${GITHUB_WORKSPACE}/target-repo"

internal/scaffold/fullsend-repo/harness/prioritize.yaml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,23 @@ host_files:
2222
expand: true
2323

2424
pre_script: scripts/pre-prioritize.sh
25+
post_script: scripts/post-prioritize.sh
2526

2627
validation_loop:
2728
script: scripts/validate-output-schema.sh
2829
max_iterations: 2
2930

30-
post_script: scripts/post-prioritize.sh
31-
3231
runner_env:
33-
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
34-
GH_TOKEN: ${GH_TOKEN}
35-
ORG: ${ORG}
36-
PROJECT_NUMBER: ${PROJECT_NUMBER}
3732
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/prioritize-result.schema.json
3833

3934
timeout_minutes: 10
35+
36+
forge:
37+
github:
38+
pre_script: scripts/pre-prioritize.sh
39+
post_script: scripts/post-prioritize.sh
40+
runner_env:
41+
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
42+
GH_TOKEN: ${GH_TOKEN}
43+
ORG: ${ORG}
44+
PROJECT_NUMBER: ${PROJECT_NUMBER}

internal/scaffold/fullsend-repo/harness/retro.yaml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,22 @@ skills:
2727
- skills/agent-scaffolding
2828

2929
pre_script: scripts/pre-retro.sh
30+
post_script: scripts/post-retro.sh
3031

3132
validation_loop:
3233
script: scripts/validate-output-schema.sh
3334
max_iterations: 2
3435

35-
post_script: scripts/post-retro.sh
36-
3736
runner_env:
38-
ORIGINATING_URL: "${ORIGINATING_URL}"
39-
REPO_FULL_NAME: "${REPO_FULL_NAME}"
40-
GH_TOKEN: "${GH_TOKEN}"
4137
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/retro-result.schema.json
4238

4339
timeout_minutes: 30
40+
41+
forge:
42+
github:
43+
pre_script: scripts/pre-retro.sh
44+
post_script: scripts/post-retro.sh
45+
runner_env:
46+
ORIGINATING_URL: "${ORIGINATING_URL}"
47+
REPO_FULL_NAME: "${REPO_FULL_NAME}"
48+
GH_TOKEN: "${GH_TOKEN}"

internal/scaffold/fullsend-repo/harness/review.yaml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ skills:
1313
- skills/code-review
1414
- skills/docs-review
1515

16-
pre_script: scripts/pre-review.sh
17-
1816
host_files:
1917
- src: env/gcp-vertex.env
2018
dest: /sandbox/workspace/.env.d/gcp-vertex.env
@@ -31,17 +29,24 @@ host_files:
3129
dest: /sandbox/workspace/prior-review.txt
3230
optional: true
3331

32+
pre_script: scripts/pre-review.sh
33+
post_script: scripts/post-review.sh
34+
3435
validation_loop:
3536
script: scripts/validate-output-schema.sh
3637
max_iterations: 2
3738

38-
post_script: scripts/post-review.sh
39-
4039
runner_env:
41-
REVIEW_TOKEN: "${REVIEW_TOKEN}"
42-
REPO_FULL_NAME: "${REPO_FULL_NAME}"
43-
PR_NUMBER: "${PR_NUMBER}"
44-
GITHUB_PR_URL: "${GITHUB_PR_URL}"
4540
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/review-result.schema.json
4641

4742
timeout_minutes: 20
43+
44+
forge:
45+
github:
46+
pre_script: scripts/pre-review.sh
47+
post_script: scripts/post-review.sh
48+
runner_env:
49+
REVIEW_TOKEN: "${REVIEW_TOKEN}"
50+
REPO_FULL_NAME: "${REPO_FULL_NAME}"
51+
PR_NUMBER: "${PR_NUMBER}"
52+
GITHUB_PR_URL: "${GITHUB_PR_URL}"

internal/scaffold/fullsend-repo/harness/triage.yaml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,21 @@ skills:
2525
- skills/issue-labels
2626

2727
pre_script: scripts/pre-triage.sh
28+
post_script: scripts/post-triage.sh
2829

2930
validation_loop:
3031
script: scripts/validate-output-schema.sh
3132
max_iterations: 2
3233

33-
post_script: scripts/post-triage.sh
34-
3534
runner_env:
36-
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
37-
GH_TOKEN: ${GH_TOKEN}
3835
FULLSEND_OUTPUT_SCHEMA: ${FULLSEND_DIR}/schemas/triage-result.schema.json
3936

4037
timeout_minutes: 10
38+
39+
forge:
40+
github:
41+
pre_script: scripts/pre-triage.sh
42+
post_script: scripts/post-triage.sh
43+
runner_env:
44+
GITHUB_ISSUE_URL: ${GITHUB_ISSUE_URL}
45+
GH_TOKEN: ${GH_TOKEN}

internal/scaffold/scaffold_test.go

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -582,20 +582,110 @@ func TestHarnessesLoadAndValidate(t *testing.T) {
582582
}
583583
t.Run(e.Name(), func(t *testing.T) {
584584
harnessPath := filepath.Join(dir, "harness", e.Name())
585-
h, err := harness.Load(harnessPath)
586-
require.NoError(t, err, "Load should succeed")
587585

588-
err = h.ResolveRelativeTo(dir)
589-
require.NoError(t, err, "ResolveRelativeTo should succeed")
586+
t.Run("Load", func(t *testing.T) {
587+
h, loadErr := harness.Load(harnessPath)
588+
require.NoError(t, loadErr, "Load should succeed")
590589

591-
err = h.ValidateFilesExist()
592-
require.NoError(t, err, "ValidateFilesExist should succeed")
590+
// Top-level pre/post scripts serve as defaults even
591+
// without forge resolution (local dev without --forge).
592+
assert.NotEmpty(t, h.PreScript, "PreScript should be set at top level as default")
593+
assert.NotEmpty(t, h.PostScript, "PostScript should be set at top level as default")
594+
assert.NotNil(t, h.Forge, "Forge map should be present")
595+
assert.Contains(t, h.Forge, "github", "Forge should have a github key")
596+
597+
resolveErr := h.ResolveRelativeTo(dir)
598+
require.NoError(t, resolveErr, "ResolveRelativeTo should succeed")
599+
600+
existErr := h.ValidateFilesExist()
601+
require.NoError(t, existErr, "ValidateFilesExist should succeed")
602+
})
603+
604+
t.Run("LoadWithOpts_github", func(t *testing.T) {
605+
h, loadErr := harness.LoadWithOpts(harnessPath, harness.LoadOpts{ForgePlatform: "github"})
606+
require.NoError(t, loadErr, "LoadWithOpts should succeed")
607+
608+
assert.Nil(t, h.Forge, "Forge should be nil after resolution")
609+
assert.NotEmpty(t, h.PreScript, "PreScript should be set after forge resolution")
610+
assert.NotEmpty(t, h.PostScript, "PostScript should be set after forge resolution")
611+
assert.NotEmpty(t, h.RunnerEnv, "RunnerEnv should be non-empty after merge")
612+
613+
resolveErr := h.ResolveRelativeTo(dir)
614+
require.NoError(t, resolveErr, "ResolveRelativeTo should succeed")
615+
616+
existErr := h.ValidateFilesExist()
617+
require.NoError(t, existErr, "ValidateFilesExist should succeed")
618+
})
593619
})
594620
loaded++
595621
}
596622
assert.True(t, loaded >= 2, "expected at least 2 harnesses, got %d", loaded)
597623
}
598624

625+
func TestHarnessForgeRunnerEnvMerge(t *testing.T) {
626+
dir := t.TempDir()
627+
err := WalkFullsendRepoAll(func(path string, content []byte) error {
628+
dest := filepath.Join(dir, path)
629+
if mkErr := os.MkdirAll(filepath.Dir(dest), 0o755); mkErr != nil {
630+
return mkErr
631+
}
632+
return os.WriteFile(dest, content, 0o644)
633+
})
634+
require.NoError(t, err, "extracting scaffold")
635+
636+
tests := []struct {
637+
file string
638+
topLevelKeys []string
639+
forgeGithubKeys []string
640+
}{
641+
{
642+
file: "triage.yaml",
643+
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
644+
forgeGithubKeys: []string{"GITHUB_ISSUE_URL", "GH_TOKEN"},
645+
},
646+
{
647+
file: "code.yaml",
648+
topLevelKeys: []string{"TARGET_BRANCH"},
649+
forgeGithubKeys: []string{"PUSH_TOKEN", "PUSH_TOKEN_SOURCE", "REPO_FULL_NAME", "ISSUE_NUMBER", "REPO_DIR"},
650+
},
651+
{
652+
file: "review.yaml",
653+
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
654+
forgeGithubKeys: []string{"REVIEW_TOKEN", "REPO_FULL_NAME", "PR_NUMBER", "GITHUB_PR_URL"},
655+
},
656+
{
657+
file: "fix.yaml",
658+
topLevelKeys: []string{"TARGET_BRANCH", "TRIGGER_SOURCE", "HUMAN_INSTRUCTION", "FIX_ITERATION", "REVIEW_BODY_FILE", "PRE_AGENT_HEAD", "FULLSEND_OUTPUT_SCHEMA", "FULLSEND_OUTPUT_FILE"},
659+
forgeGithubKeys: []string{"PUSH_TOKEN", "PUSH_TOKEN_SOURCE", "REPO_FULL_NAME", "PR_NUMBER", "REPO_DIR"},
660+
},
661+
{
662+
file: "retro.yaml",
663+
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
664+
forgeGithubKeys: []string{"ORIGINATING_URL", "REPO_FULL_NAME", "GH_TOKEN"},
665+
},
666+
{
667+
file: "prioritize.yaml",
668+
topLevelKeys: []string{"FULLSEND_OUTPUT_SCHEMA"},
669+
forgeGithubKeys: []string{"GITHUB_ISSUE_URL", "GH_TOKEN", "ORG", "PROJECT_NUMBER"},
670+
},
671+
}
672+
673+
for _, tt := range tests {
674+
t.Run(tt.file, func(t *testing.T) {
675+
harnessPath := filepath.Join(dir, "harness", tt.file)
676+
h, loadErr := harness.LoadWithOpts(harnessPath, harness.LoadOpts{ForgePlatform: "github"})
677+
require.NoError(t, loadErr)
678+
679+
for _, key := range tt.topLevelKeys {
680+
assert.Contains(t, h.RunnerEnv, key, "merged RunnerEnv should contain top-level key %s", key)
681+
}
682+
for _, key := range tt.forgeGithubKeys {
683+
assert.Contains(t, h.RunnerEnv, key, "merged RunnerEnv should contain forge.github key %s", key)
684+
}
685+
})
686+
}
687+
}
688+
599689
func TestRepoMaintenanceWorkflowContent(t *testing.T) {
600690
content, err := FullsendRepoFile(".github/workflows/repo-maintenance.yml")
601691
require.NoError(t, err)

0 commit comments

Comments
 (0)