fix(install): refuse --path only for colliding tools; re-land installer test suite (#772) (#828)

* Reapply "test(install): add a regression suite for install.sh + CI on Linux and macOS (#772)" (#827)

This reverts commit 4bab3cf4a2.

* fix(install): refuse --path only for tools that would overwrite each other; re-land installer test suite (#772)

Re-lands the install.sh regression suite from #772 (reverted in #827) together
with the guard change that makes it pass, so CI goes green in one step.

Background. #825 made --path refuse more than one --tool, on #819's report that
several tools sharing one destination clobber each other. That was right in
spirit and over-broad in practice, and it was implemented without verifying
the premise. Measured by installing one agent with every tool into a sandbox
and comparing what landed:

  <division>-<slug>.md  (raw copy)   claude-code, copilot
  <slug>.md             (converted)  gemini-cli, opencode, qwen, zcode
  agency-<slug>/SKILL.md             antigravity, osaurus

Tools in the same group write identical filenames and silently overwrite each
other (qwen + gemini-cli lose a file while both print [OK]). Tools in different
groups coexist (claude-code + codex, claude-code + qwen). Every other tool's
output is distinct.

The guard now refuses --path only for a colliding pair, naming both tools and
the reason, and allows the rest. path_collision_group() holds the measured
table; re-measure if a converter's naming changes.

The suite's two-tool --path cases used claude-code + copilot, which collide:
the "installs exactly one agent" count of 1 was passing because copilot had
overwritten claude-code's identical file. They now use claude-code + codex and
assert that BOTH outputs survive, which a single count cannot show; a new case
asserts the colliding pair is refused. codex has no committed output, so those
cases convert (only the two raw-copiers work under --no-convert in a fresh
checkout).

Verified under bash 3.2 (the macOS CI leg): 29 passed, 0 failed, 1 xfail.
Guard spot-checked against all three measured groups plus a cross-group pair.

Suite, workflow and CONTRIBUTING note by @SergiorCode (#772). Refs #772 #819
#825 #827.

Co-Authored-By: SergiorCode <SergiorCode@users.noreply.github.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(install): auto-convert never fired in a fresh checkout — README.md read as generated output

ensure_converted decided a converted tool's output was present if its
integrations/<tool>/ directory contained any file. Every one of those
directories ships a committed README.md, so in a fresh checkout the check
always found a file, skipped convert.sh, and the installer then hard-failed
"integrations/<tool> missing. Run convert.sh first." — the exact flow #426's
auto-convert was added to prevent. This affected every converted tool; it was
masked locally by generated outputs left behind in the working tree, and
surfaced only when the test suite ran in a clean checkout.

Only files other than README.md now count as generated output. Verified in a
clean worktree under bash 3.2: the suite's serial-control case (claude-code +
codex into one --path) now auto-converts codex and both outputs land;
29 passed, 0 failed, 1 xfail.

Refs #426

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: SergiorCode <SergiorCode@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Michael Sitarzewski
2026-09-03 07:15:08 -05:00
committed by GitHub
co-authored by Claude Fable 5.1 SergiorCode
parent 4bab3cf4a2
commit 128565a828
4 changed files with 390 additions and 5 deletions
+34 -5
View File
@@ -283,6 +283,20 @@ install_file() {
}
# resolve_dest <tool> <default> — --path > $ENV_VAR > default.
# path_collision_group <tool> — tools in the same group write identical
# filenames into a shared --path and would overwrite each other; empty means
# the tool's output is distinct and may share a path with anything. Derived by
# installing one agent with every tool into a sandbox and comparing what
# landed; re-measure if a converter's output naming changes.
path_collision_group() {
case "$1" in
claude-code|copilot) printf 'raw-source-md' ;; # <division>-<slug>.md
gemini-cli|opencode|qwen|zcode) printf 'slug-md' ;; # <slug>.md
antigravity|osaurus) printf 'agency-skill' ;; # agency-<slug>/SKILL.md
*) printf '' ;;
esac
}
resolve_dest() {
local tool="$1" def="$2" var=""
[[ -n "$OVERRIDE_PATH" ]] && { printf '%s' "$OVERRIDE_PATH"; return; }
@@ -324,7 +338,11 @@ ensure_converted() {
$AUTO_CONVERT || return 0
case "$tool" in claude-code|copilot) return 0 ;; esac
local d="$INTEGRATIONS/$tool"
if [[ ! -d "$d" ]] || [[ -z "$(find "$d" -type f 2>/dev/null | head -1)" ]]; then
# Every integrations/<tool>/ ships a committed README.md, so "any file
# present" mistook the README for generated output and never converted in a
# fresh checkout (the installer then hard-failed "<tool> missing"). Only files
# other than the README count as output.
if [[ ! -d "$d" ]] || [[ -z "$(find "$d" -type f ! -name 'README.md' 2>/dev/null | head -1)" ]]; then
warn "$tool: integration files missing — running convert.sh --tool $tool"
"$SCRIPT_DIR/convert.sh" --tool "$tool" >/dev/null 2>&1 \
&& ok "$tool: generated integration files" \
@@ -1258,11 +1276,22 @@ main() {
$duplicate || _cleaned+=("$_t")
done
_tool_list=("${_cleaned[@]}")
# --path is a single-destination override; with several tools every one of
# them would land in the same directory and clobber each other.
# --path is one shared directory. Tools that write the same filenames into
# it silently overwrite each other; tools with distinct outputs coexist.
# Refuse only the colliding combinations (see path_collision_group).
if [[ -n "$OVERRIDE_PATH" && ${#_tool_list[@]} -gt 1 ]]; then
err "--path sets ONE destination; use it with exactly one --tool (got ${#_tool_list[@]}: ${_tool_list[*]})."
exit 1
local _ta _tb _ga _gb
for _ta in "${_tool_list[@]}"; do
_ga="$(path_collision_group "$_ta")"; [[ -z "$_ga" ]] && continue
for _tb in "${_tool_list[@]}"; do
[[ "$_tb" == "$_ta" ]] && continue
_gb="$(path_collision_group "$_tb")"
if [[ "$_ga" == "$_gb" ]]; then
err "--path is one shared directory, and $_ta and $_tb write the same filenames into it — they would overwrite each other. Use one of them per --path (tools with distinct outputs may share one)."
exit 1
fi
done
done
fi
fi