Files
boshu2__agentops/cli/internal/gates/applicability.go
T
Bo a4c0f2d646 fix(cli): three novice-test majors — foreign-repo gates, symlinked skills, benign PATH shadow (#940)
Wave 1 of the new-user happy-path fixes (fresh-eyes novice test,
follow-up to the [3.3 release
audit](docs/audits/release-readiness-3.3-2026-07-20.md)).

**Edge 1 — `ao gate check` in a foreign repo.** Was: ~10 blocking
UNKNOWN rows naming agentops-internal scripts, a summary that counted
none of them, exit 1 — the product's own suggested next command failed
in every repo but this one. Now: a deterministic repo marker
(`cli/go.mod` module identity) routes those checks to first-class
not-applicable SKIPs outside agentops; human output aggregates them into
one honest line and suppresses the routing-skip wall (70 lines → 5); the
summary counts UNKNOWN in its own bucket everywhere; `--json` keeps
every row. Inside agentops, a missing backing script stays fail-closed
UNKNOWN (guard pinned by test).

**Edge 2 — permanent false `fm-skills-missing` P1.** The detector's
Lstat-based scan counted the canonical `ao skills link` symlink farm as
zero skills. Now resolves symlinked skill dirs; dangling links still
count as absent.

**Edge 3 — P1 on every default dev Mac.** Benign PATH shadowing
(Homebrew git + Apple `/usr/bin/git`) raised a P1 with a broken
empty-slot remediation ("Install the missing CLI yourself — —").
Shadow-only now produces no finding; the missing-CLI template renders
only with real names.

**Process:** three scoped implementer agents (disjoint file ownership) +
fresh adversarial verifier (4× RESOLVED, incl. dangling-symlink and
genuinely-missing-CLI negative cases). **Verified:** per-fix RED→GREEN
shell repros; foreign-repo first contact now 5 lines + exit 0 with JSON
parity; 61/61 test pkgs; golangci-lint clean; `ao gate check --full`
67/67 over this range.
2026-07-20 15:36:01 -04:00

47 lines
1.9 KiB
Go

package gates
import (
"os"
"path/filepath"
"strings"
)
// agentopsCLIModulePath is the module path declared by the agentops repo's own
// CLI module. Its presence at <root>/cli/go.mod is the deterministic
// "this IS the agentops repo" marker used by NotApplicableOutsideAgentOps.
const agentopsCLIModulePath = "github.com/boshu2/agentops/cli"
// NotApplicableReason is the first-class SKIP reason emitted when an
// agentops-internal check runs in a foreign repository whose tree does not
// contain the check's backing artifact. Report aggregation keys on this exact
// string, so it is a single shared constant.
const NotApplicableReason = "not applicable: agentops-repo check; backing artifact not present in this repository"
// IsAgentOpsRepo reports whether root is the agentops repository itself.
//
// Marker: <root>/cli/go.mod whose `module` directive is exactly
// github.com/boshu2/agentops/cli. This is unambiguous (module paths are
// globally unique by convention and this one names the agentops repo),
// deterministic (a plain tracked file, present in every checkout and linked
// worktree), and cheap (one bounded file read). It deliberately does NOT use
// git metadata (remotes can be renamed or absent) or the presence of scripts/
// (which is exactly what a foreign repo may or may not have).
//
// Inside the agentops repo a missing backing script stays UNKNOWN and
// fail-closed — that guard protects agentops CI from silently losing a gate.
// Outside it, the same condition is a first-class not-applicable SKIP
// (see ScriptRunner.Run).
func IsAgentOpsRepo(root string) bool {
raw, err := os.ReadFile(filepath.Join(root, "cli", "go.mod"))
if err != nil {
return false
}
for _, line := range strings.Split(string(raw), "\n") {
line = strings.TrimSpace(line)
if rest, ok := strings.CutPrefix(line, "module "); ok {
return strings.TrimSpace(rest) == agentopsCLIModulePath
}
}
return false
}