Skip to content

Commit d93770d

Browse files
ggallenclaude
andcommitted
fix: load per-repo config.yaml in fullsend run and reusable workflows
Per-repo installations store config.yaml in .fullsend/ but the CLI only parsed OrgConfig format. Add OrgConfigFromPerRepo adapter and teach tryLoadFullsendConfig/requireFullsendConfig to fall back to PerRepoConfig parsing using structural discrimination (isPerRepoYAML). Update all six reusable workflows to layer workspace files under .fullsend/ when install_mode is per-repo, and pass fullsend-dir to the action invocation. Closes #2970 Signed-off-by: Greg Allen <greg@fullsend.ai> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Greg Allen <gallen@redhat.com>
1 parent 6cfeae9 commit d93770d

12 files changed

Lines changed: 365 additions & 56 deletions

.github/workflows/reusable-code.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,19 @@ jobs:
8888
run: |
8989
set -euo pipefail
9090
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
91-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
91+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9292
exit 1
9393
fi
9494
SRC=".defaults/internal/scaffold/fullsend-repo"
9595
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
96+
DEST=""
97+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
98+
DEST=".fullsend/"
99+
fi
96100
for dir in ${LAYERED_DIRS}; do
97101
if [[ -d "${SRC}/${dir}" ]]; then
98-
mkdir -p "${dir}"
99-
cp -r "${SRC}/${dir}/." "${dir}/"
102+
mkdir -p "${DEST}${dir}"
103+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
100104
fi
101105
done
102106
CUSTOM_BASE="customized"
@@ -108,8 +112,8 @@ jobs:
108112
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
109113
| while IFS= read -r -d '' f; do
110114
rel="${f#"${CUSTOM_BASE}"/}"
111-
mkdir -p "$(dirname "${rel}")"
112-
cp "${f}" "${rel}"
115+
mkdir -p "$(dirname "${DEST}${rel}")"
116+
cp "${f}" "${DEST}${rel}"
113117
done
114118
fi
115119
done
@@ -195,6 +199,7 @@ jobs:
195199
with:
196200
agent: code
197201
version: ${{ inputs.fullsend_version }}
202+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
198203
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
199204
status-repo: ${{ inputs.source_repo }}
200205
status-number: ${{ fromJSON(inputs.event_payload).issue.number }}

.github/workflows/reusable-fix.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,19 @@ jobs:
104104
run: |
105105
set -euo pipefail
106106
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
107-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
107+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
108108
exit 1
109109
fi
110110
SRC=".defaults/internal/scaffold/fullsend-repo"
111111
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
112+
DEST=""
113+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
114+
DEST=".fullsend/"
115+
fi
112116
for dir in ${LAYERED_DIRS}; do
113117
if [[ -d "${SRC}/${dir}" ]]; then
114-
mkdir -p "${dir}"
115-
cp -r "${SRC}/${dir}/." "${dir}/"
118+
mkdir -p "${DEST}${dir}"
119+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
116120
fi
117121
done
118122
CUSTOM_BASE="customized"
@@ -124,8 +128,8 @@ jobs:
124128
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
125129
| while IFS= read -r -d '' f; do
126130
rel="${f#"${CUSTOM_BASE}"/}"
127-
mkdir -p "$(dirname "${rel}")"
128-
cp "${f}" "${rel}"
131+
mkdir -p "$(dirname "${DEST}${rel}")"
132+
cp "${f}" "${DEST}${rel}"
129133
done
130134
fi
131135
done
@@ -396,6 +400,7 @@ jobs:
396400
with:
397401
agent: fix
398402
version: ${{ inputs.fullsend_version }}
403+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
399404
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
400405
status-repo: ${{ inputs.source_repo }}
401406
status-number: ${{ steps.context.outputs.pr_number }}

.github/workflows/reusable-prioritize.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,19 @@ jobs:
8989
run: |
9090
set -euo pipefail
9191
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
92-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
92+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9393
exit 1
9494
fi
9595
SRC=".defaults/internal/scaffold/fullsend-repo"
9696
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
97+
DEST=""
98+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
99+
DEST=".fullsend/"
100+
fi
97101
for dir in ${LAYERED_DIRS}; do
98102
if [[ -d "${SRC}/${dir}" ]]; then
99-
mkdir -p "${dir}"
100-
cp -r "${SRC}/${dir}/." "${dir}/"
103+
mkdir -p "${DEST}${dir}"
104+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
101105
fi
102106
done
103107
CUSTOM_BASE="customized"
@@ -109,8 +113,8 @@ jobs:
109113
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
110114
| while IFS= read -r -d '' f; do
111115
rel="${f#"${CUSTOM_BASE}"/}"
112-
mkdir -p "$(dirname "${rel}")"
113-
cp "${f}" "${rel}"
116+
mkdir -p "$(dirname "${DEST}${rel}")"
117+
cp "${f}" "${DEST}${rel}"
114118
done
115119
fi
116120
done
@@ -151,4 +155,5 @@ jobs:
151155
with:
152156
agent: prioritize
153157
version: ${{ inputs.fullsend_version }}
158+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
154159
mint-url: ${{ inputs.mint_url }}

.github/workflows/reusable-retro.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,19 @@ jobs:
8585
run: |
8686
set -euo pipefail
8787
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
88-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
88+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
8989
exit 1
9090
fi
9191
SRC=".defaults/internal/scaffold/fullsend-repo"
9292
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
93+
DEST=""
94+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
95+
DEST=".fullsend/"
96+
fi
9397
for dir in ${LAYERED_DIRS}; do
9498
if [[ -d "${SRC}/${dir}" ]]; then
95-
mkdir -p "${dir}"
96-
cp -r "${SRC}/${dir}/." "${dir}/"
99+
mkdir -p "${DEST}${dir}"
100+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
97101
fi
98102
done
99103
CUSTOM_BASE="customized"
@@ -105,8 +109,8 @@ jobs:
105109
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
106110
| while IFS= read -r -d '' f; do
107111
rel="${f#"${CUSTOM_BASE}"/}"
108-
mkdir -p "$(dirname "${rel}")"
109-
cp "${f}" "${rel}"
112+
mkdir -p "$(dirname "${DEST}${rel}")"
113+
cp "${f}" "${DEST}${rel}"
110114
done
111115
fi
112116
done
@@ -162,6 +166,7 @@ jobs:
162166
with:
163167
agent: retro
164168
version: ${{ inputs.fullsend_version }}
169+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
165170
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
166171
status-repo: ${{ inputs.source_repo }}
167172
status-number: ${{ fromJSON(inputs.event_payload).pull_request.number || fromJSON(inputs.event_payload).issue.number }}

.github/workflows/reusable-review.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,19 @@ jobs:
8686
run: |
8787
set -euo pipefail
8888
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
89-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
89+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9090
exit 1
9191
fi
9292
SRC=".defaults/internal/scaffold/fullsend-repo"
9393
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
94+
DEST=""
95+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
96+
DEST=".fullsend/"
97+
fi
9498
for dir in ${LAYERED_DIRS}; do
9599
if [[ -d "${SRC}/${dir}" ]]; then
96-
mkdir -p "${dir}"
97-
cp -r "${SRC}/${dir}/." "${dir}/"
100+
mkdir -p "${DEST}${dir}"
101+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
98102
fi
99103
done
100104
CUSTOM_BASE="customized"
@@ -106,8 +110,8 @@ jobs:
106110
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
107111
| while IFS= read -r -d '' f; do
108112
rel="${f#"${CUSTOM_BASE}"/}"
109-
mkdir -p "$(dirname "${rel}")"
110-
cp "${f}" "${rel}"
113+
mkdir -p "$(dirname "${DEST}${rel}")"
114+
cp "${f}" "${DEST}${rel}"
111115
done
112116
fi
113117
done
@@ -176,6 +180,7 @@ jobs:
176180
PRIOR_REVIEW_PROVENANCE: ${{ steps.prior-review.outputs.prior_review_provenance }}
177181
with:
178182
agent: review
183+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
179184
version: ${{ inputs.fullsend_version }}
180185
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
181186
status-repo: ${{ inputs.source_repo }}

.github/workflows/reusable-triage.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,19 @@ jobs:
8686
run: |
8787
set -euo pipefail
8888
if [[ "${INSTALL_MODE}" != "per-org" && "${INSTALL_MODE}" != "per-repo" ]]; then
89-
echo "::error::Invalid install_mode '${INSTALL_MODE}': must be 'per-org' or 'per-repo'"
89+
echo "::error::Invalid install_mode: must be 'per-org' or 'per-repo'"
9090
exit 1
9191
fi
9292
SRC=".defaults/internal/scaffold/fullsend-repo"
9393
LAYERED_DIRS="agents skills schemas harness plugins policies scripts env"
94+
DEST=""
95+
if [[ "${INSTALL_MODE}" == "per-repo" ]]; then
96+
DEST=".fullsend/"
97+
fi
9498
for dir in ${LAYERED_DIRS}; do
9599
if [[ -d "${SRC}/${dir}" ]]; then
96-
mkdir -p "${dir}"
97-
cp -r "${SRC}/${dir}/." "${dir}/"
100+
mkdir -p "${DEST}${dir}"
101+
cp -r "${SRC}/${dir}/." "${DEST}${dir}/"
98102
fi
99103
done
100104
CUSTOM_BASE="customized"
@@ -106,8 +110,8 @@ jobs:
106110
find "${CUSTOM_BASE}/${dir}" -type f ! -name '.gitkeep' -print0 \
107111
| while IFS= read -r -d '' f; do
108112
rel="${f#"${CUSTOM_BASE}"/}"
109-
mkdir -p "$(dirname "${rel}")"
110-
cp "${f}" "${rel}"
113+
mkdir -p "$(dirname "${DEST}${rel}")"
114+
cp "${f}" "${DEST}${rel}"
111115
done
112116
fi
113117
done
@@ -161,6 +165,7 @@ jobs:
161165
with:
162166
agent: triage
163167
version: ${{ inputs.fullsend_version }}
168+
fullsend-dir: ${{ inputs.install_mode == 'per-repo' && '.fullsend' || '' }}
164169
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
165170
status-repo: ${{ inputs.source_repo }}
166171
status-number: ${{ fromJSON(inputs.event_payload).issue.number }}

docs/plans/deprecate-per-org-install.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,11 @@ All per-org commands gone.
440440
into the renamed `Config` struct and `ParseConfig()`. This field is
441441
accessed by `run.go` (~lines 189, 192, 201, 205, 237, 243) and
442442
`lock.go` (~lines 170, 173, 181, 185, 264, 268, 270) via
443-
`tryLoadOrgConfig()`/`requireOrgConfig()` in `orgconfig.go`. Update
444-
`orgconfig.go` to use `config.ParseConfig()` instead of
445-
`config.ParseOrgConfig()` and rename functions/file accordingly
446-
(e.g. `tryLoadConfig()`/`requireConfig()`, rename file to
443+
`tryLoadFullsendConfig()`/`requireFullsendConfig()` (renamed from
444+
`tryLoadOrgConfig()`/`requireOrgConfig()`, which remain as var
445+
aliases) in `orgconfig.go`. Update `orgconfig.go` to use
446+
`config.ParseConfig()` instead of `config.ParseOrgConfig()` and
447+
rename functions/file accordingly (e.g. rename file to
447448
`configloader.go` or inline into callers).
448449

449450
**Update all callers:**
@@ -468,11 +469,12 @@ All per-org commands gone.
468469
`DefaultRoles()` for consistency. Effectively dead code after
469470
per-org removal.
470471
- `internal/cli/orgconfig.go`: Update `config.ParseOrgConfig()` calls
471-
(~lines 22, 42) to `config.ParseConfig()`. Rename
472-
`tryLoadOrgConfig()``tryLoadConfig()`, `requireOrgConfig()`
473-
`requireConfig()`. Rename file to `configloader.go` or inline.
474-
- `internal/cli/run.go`: Update `tryLoadOrgConfig()`/
475-
`requireOrgConfig()` calls (~lines 189, 198, 201, 235, 237) and
472+
to `config.ParseConfig()`. The functions were already renamed to
473+
`tryLoadFullsendConfig()`/`requireFullsendConfig()` (PR #3000); var
474+
aliases `tryLoadOrgConfig`/`requireOrgConfig` can be removed.
475+
Rename file to `configloader.go` or inline.
476+
- `internal/cli/run.go`: Update `tryLoadOrgConfig`/
477+
`requireOrgConfig` var alias calls (~lines 189, 198, 201, 235, 237) and
476478
`orgCfg.AllowedRemoteResources` accesses (~lines 192, 205, 243)
477479
to use the renamed config type and loader functions. Also update
478480
`config.ParseOrgConfig()` (~line 1974): this call reads
@@ -483,8 +485,8 @@ All per-org commands gone.
483485
and update `ParseConfig()` to populate it. This keeps status
484486
notification configuration available in per-repo mode without
485487
importing the full `RepoDefaults` sub-struct.
486-
- `internal/cli/lock.go`: Update `tryLoadOrgConfig()`/
487-
`requireOrgConfig()` calls (~lines 170, 181, 264) and
488+
- `internal/cli/lock.go`: Update `tryLoadOrgConfig`/
489+
`requireOrgConfig` var alias calls (~lines 170, 181, 264) and
488490
`orgCfg.AllowedRemoteResources` accesses (~lines 173, 185, 268,
489491
270) to use the renamed config type and loader functions.
490492
- Any other files importing `config.PerRepoConfig` or

internal/cli/lock_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1079,7 +1079,7 @@ func TestRunLock_URLRefsNoOrgConfigError(t *testing.T) {
10791079
printer := ui.New(os.Stdout)
10801080
err := runLock(context.Background(), "noconfig", dir, "", false, resolveFlags{}, printer)
10811081
require.Error(t, err)
1082-
assert.Contains(t, err.Error(), "URL-referenced resources require an org-level config.yaml")
1082+
assert.Contains(t, err.Error(), "URL-referenced resources require a config.yaml")
10831083
assert.Contains(t, err.Error(), "allowed_remote_resources")
10841084
}
10851085

0 commit comments

Comments
 (0)