feat: Auth + Logging Architecture Overhaul (#393)#394
feat: Auth + Logging Architecture Overhaul (#393)#394danielmeppiel wants to merge 29 commits intomainfrom
Conversation
Phase 1 — Foundation: - AuthResolver: per-(host,org) token resolution, host classification, try_with_fallback(), build_error_context(), detect_token_type() - CommandLogger base + InstallLogger subclass with semantic lifecycle - DiagnosticCollector: CATEGORY_AUTH, auth() method, render group Phase 2 — Auth wiring (all touchpoints): - github_downloader.py: AuthResolver injected, per-dep token resolution in _clone_with_fallback and _download_github_file - install.py: _validate_package_exists uses try_with_fallback(unauth_first=True) - copilot.py + operations.py: replaced os.getenv bypasses with TokenManager - All 7 hardcoded auth error messages replaced with build_error_context() Security fix: global env vars (GITHUB_APM_PAT) no longer leak to non-default hosts (GHES, GHE Cloud, generic). Enterprise hosts resolve via per-org env vars or git credential helpers only. Phase 4 — Tests: - test_auth.py: 34 tests (classify_host, resolve, try_with_fallback, build_error_context, detect_token_type) - test_command_logger.py: 44 tests (CommandLogger, InstallLogger, _ValidationOutcome) - Fixed test_github_downloader_token_precedence.py import paths - Updated test_auth_scoping.py for AuthResolver integration Phase 5 — Agents & Skills: - 3 agent personas: auth-expert, python-architect, cli-logging-expert - 2 new skills: auth, python-architecture - Updated cli-logging-ux skill with CommandLogger awareness All 2829 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Install UX overhaul (3a): - Wire InstallLogger into install.py with semantic lifecycle methods - Short-circuit on total validation failure (no more misleading counts) - Context-aware messages: partial vs full, new vs locked dependencies - Demote noise (lockfile info, MCP transitive, orphan cleanup) to verbose Documentation (5a, 5b): - Rewrite authentication.md: resolution chain, token lookup table, per-org setup, EMU/GHE Cloud/GHES sections, troubleshooting - CHANGELOG entry under [Unreleased] for #393 Test updates (4c): - 11 new CATEGORY_AUTH tests in test_diagnostics.py - Fix tuple unpacking in test_canonicalization, test_dev_dependencies, test_generic_git_url_install (validation now returns outcome) - Update error message assertion in test_install_command All 2839 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 3 — Logging migration (3b-3g): - compile: CommandLogger in cli.py, _log() helper in agents_compiler.py - deps: CommandLogger in cli.py + _utils.py (logger=None backward compat) - audit: CommandLogger, pass logger to helpers, verbose_detail for scan stats - mcp/run/init: CommandLogger with --verbose support - config/list/update/pack/runtime/prune/uninstall: CommandLogger wiring - Support modules: logger=None param in skill/prompt/instruction/mcp integrators, vscode adapter, drift module Phase 4 — Integration tests (4d, 4e): - test_auth_resolver.py: 26 integration tests across 8 test classes - Scripts verified — no auth error pattern updates needed All 2839 tests pass, 126 skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces two cross-cutting foundations—AuthResolver (per-(host, org) auth resolution) and CommandLogger / InstallLogger (structured CLI lifecycle logging)—and wires them through CLI commands and auth touchpoints to fix EMU/private repo installs, reduce confusing output, and prevent token leakage across hosts.
Changes:
- Add
AuthResolver+ host classification + fallback strategies; integrate into clone/download/validation paths. - Add
CommandLogger/InstallLoggerand thread diagnostics through commands and supporting modules. - Expand diagnostics with
CATEGORY_AUTH, update docs/changelog, and adjust/add unit & integration tests.
Reviewed changes
Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_unpacker.py | Updates dry-run output assertion to match new logging wording. |
| tests/unit/test_install_command.py | Updates invalid-format assertion to match updated validation messaging. |
| tests/unit/test_diagnostics.py | Adds coverage for new CATEGORY_AUTH diagnostics group rendering/counting. |
| tests/unit/test_dev_dependencies.py | Adapts to new (validated, outcome) return from validation helper. |
| tests/unit/test_command_logger.py | New unit tests for CommandLogger, InstallLogger, _ValidationOutcome. |
| tests/unit/test_canonicalization.py | Updates expectations for new validation helper return type. |
| tests/unit/test_auth_scoping.py | Adjusts scoping tests to align with per-dep auth resolution & cache. |
| tests/unit/test_auth.py | New unit tests for AuthResolver (classification, caching, fallback, errors). |
| tests/unit/test_audit_command.py | Updates audit helper calls to pass CommandLogger. |
| tests/test_token_manager.py | Adds tests for configurable credential-helper timeout behavior. |
| tests/test_github_downloader_token_precedence.py | Fixes test imports to use installed package paths. |
| tests/integration/test_generic_git_url_install.py | Updates expectations for new validation helper return type. |
| tests/integration/test_auth_resolver.py | New integration tests gated by APM_E2E_TESTS. |
| src/apm_cli/utils/diagnostics.py | Adds CATEGORY_AUTH + rendering and count helpers. |
| src/apm_cli/registry/operations.py | Uses token manager precedence when auto-filling env vars in CI/E2E mode. |
| src/apm_cli/integration/skill_integrator.py | Threads optional logger into skill integration warnings/diagnostics. |
| src/apm_cli/integration/prompt_integrator.py | Adds optional logger parameter for structured output compatibility. |
| src/apm_cli/integration/mcp_integrator.py | Threads optional logger through MCP install/remove flows; renames module logger to _log. |
| src/apm_cli/integration/instruction_integrator.py | Adds optional logger parameter for structured output compatibility. |
| src/apm_cli/drift.py | Threads optional logger through drift helpers. |
| src/apm_cli/deps/github_downloader.py | Switches to AuthResolver and per-dependency token resolution for clone/download/error context. |
| src/apm_cli/core/token_manager.py | Makes git credential fill timeout configurable; default 60s, max 180s. |
| src/apm_cli/core/command_logger.py | New structured logging base (CommandLogger) and install-specific logger (InstallLogger). |
| src/apm_cli/core/auth.py | New AuthResolver/AuthContext/HostInfo with caching + fallback strategies. |
| src/apm_cli/core/init.py | Exposes auth types from apm_cli.core. |
| src/apm_cli/compilation/agents_compiler.py | Adds optional logger plumbing for compilation output instead of raw print(). |
| src/apm_cli/commands/update.py | Migrates update command output to CommandLogger. |
| src/apm_cli/commands/uninstall/cli.py | Migrates uninstall command output to CommandLogger. |
| src/apm_cli/commands/runtime.py | Migrates runtime command output to CommandLogger. |
| src/apm_cli/commands/run.py | Adds --verbose and migrates output to CommandLogger. |
| src/apm_cli/commands/prune.py | Migrates prune command output to CommandLogger. |
| src/apm_cli/commands/pack.py | Migrates pack/unpack output to CommandLogger (incl. dry-run messaging). |
| src/apm_cli/commands/mcp.py | Adds --verbose and migrates MCP commands output to CommandLogger. |
| src/apm_cli/commands/list_cmd.py | Migrates list command output to CommandLogger. |
| src/apm_cli/commands/install.py | Integrates InstallLogger, introduces validation outcome tracking, and auth-aware validation (try_with_fallback). |
| src/apm_cli/commands/init.py | Adds --verbose and migrates init output to CommandLogger. |
| src/apm_cli/commands/deps/cli.py | Migrates deps subcommands output to CommandLogger. |
| src/apm_cli/commands/deps/_utils.py | Threads optional logger into deps update helpers and removes direct console helper usage. |
| src/apm_cli/commands/config.py | Migrates config command output to CommandLogger. |
| src/apm_cli/commands/compile/cli.py | Migrates compile output to CommandLogger and passes logger into compiler. |
| src/apm_cli/commands/audit.py | Migrates audit output to CommandLogger; threads logger into helpers. |
| src/apm_cli/adapters/client/vscode.py | Threads optional logger through VS Code MCP config operations. |
| src/apm_cli/adapters/client/copilot.py | Uses token manager for Copilot header token selection. |
| docs/src/content/docs/guides/private-packages.md | Updates links to auth + git reference docs. |
| docs/src/content/docs/getting-started/authentication.md | Rewrites auth docs to describe per-(host, org) resolution and security constraint. |
| CHANGELOG.md | Adds Keep-a-Changelog entry for new auth/logging architecture and timeout fix. |
| .github/skills/python-architecture/SKILL.md | Adds skill trigger guidance for architecture changes. |
| .github/skills/cli-logging-ux/SKILL.md | Updates logging UX skill to include CommandLogger rules. |
| .github/skills/auth/SKILL.md | Adds skill trigger guidance and “AuthResolver-only” rule. |
| .github/agents/python-architect.agent.md | Adds agent persona for architecture review. |
| .github/agents/cli-logging-expert.agent.md | Adds agent persona for CLI logging UX review. |
| .github/agents/auth-expert.agent.md | Adds agent persona for authentication review. |
| logger.resolution_start( | ||
| to_install_count=len(packages) if packages else 0, | ||
| lockfile_count=0, # Refined later inside _install_apm_dependencies | ||
| ) |
There was a problem hiding this comment.
InstallLogger.resolution_start() is called with to_install_count=len(packages), which counts user-supplied arguments rather than new packages to install. If the package was already present in apm.yml, the logger will still print "Installing 1 new package...", which is misleading. Use the validation outcome (e.g., len(outcome.new_packages)) or the actual resolved install set for this count.
There was a problem hiding this comment.
Fixed in efa13c1 — now uses len(validated_packages) instead of len(packages), so the count reflects packages that actually passed validation.
- Fix docs conflating EMU with *.ghe.com — EMU orgs can exist on github.com too; *.ghe.com is specifically GHE Cloud Data Residency - Increase git credential fill timeout: 5s → 60s default (configurable via APM_GIT_CREDENTIAL_TIMEOUT, max 180s) — fixes silent auth failures on Windows when credential helper shows interactive account picker - Add 7 timeout tests - CHANGELOG entry for credential timeout fix Credit: credential timeout fix based on investigation by @frblondin in #389. Co-authored-by: Thomas Caudal <thomas@caudal.fr> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ghu_ is OAuth user-to-server (e.g. gh auth login), NOT EMU. EMU users get regular ghp_ (classic) or github_pat_ (fine-grained) tokens — there is no prefix that identifies EMU. EMU is a property of the account, not the token format. Changes: - detect_token_type: ghu_ → 'oauth', gho_ → 'oauth', ghs_/ghr_ → 'github-app' (was all 'classic') - build_error_context: replace token_type=='emu' check with host-based heuristics (*.ghe.com → enterprise msg, github.com → SAML/EMU mention) - Auth docs: remove ghu_ as EMU prefix, clarify EMU uses standard PATs - Agent persona: correct token prefix reference table - Tests: update detect_token_type + build_error_context assertions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0e12ae5 to
6ddaea4
Compare
Global env vars (GITHUB_APM_PAT, GITHUB_TOKEN, GH_TOKEN) now apply to all hosts — not just the default host. HTTPS is the real transport security boundary; host-gating was unnecessary and forced users into awkward per-org token setups. When a global token doesn't work for a particular host (e.g. github.com PAT on *.ghe.com), try_with_fallback() retries with git credential fill before giving up. This preserves seamless behavior for gh auth login users with multiple hosts. Changes: - auth.py: remove _is_default gate in _resolve_token(), add _try_credential_fallback() in try_with_fallback() - authentication.md: remove Security constraint section, simplify token table, fix EMU example, add HTTPS note - 5 new tests, 3 updated tests (2851 passing) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
resolution_start() was using len(packages) (user-supplied arguments) instead of len(validated_packages) (packages that passed validation). This caused misleading 'Installing N new packages' when some packages failed validation or were already in apm.yml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 52 out of 52 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
src/apm_cli/core/command_logger.py:174
- InstallLogger.validation_pass()/validation_fail() embed Unicode check/cross characters directly in the output strings. Since the console helpers already support ASCII-safe symbols via
symbol=...(seeSTATUS_SYMBOLS), consider switching these to usesymbolinstead of inline glyphs to keep output consistent and Windows-friendly.
def validation_pass(self, canonical: str, already_present: bool):
"""Log a package that passed validation."""
if already_present:
_rich_echo(f" ✓ {canonical} (already in apm.yml)", color="dim")
else:
_rich_success(f" ✓ {canonical}")
def validation_fail(self, package: str, reason: str):
"""Log a package that failed validation."""
_rich_error(f" ✗ {package} — {reason}")
| elif 'token' in var_name.lower() or 'key' in var_name.lower(): | ||
| # For tokens/keys, try environment defaults with fallback chain | ||
| # Priority: GITHUB_APM_PAT (APM modules) > GITHUB_TOKEN (user tokens) | ||
| env_vars[var_name] = os.getenv('GITHUB_APM_PAT') or os.getenv('GITHUB_TOKEN', '') | ||
| # Use centralized token manager for consistent precedence | ||
| # (GITHUB_APM_PAT → GITHUB_TOKEN → GH_TOKEN) | ||
| _tm = GitHubTokenManager() | ||
| env_vars[var_name] = _tm.get_token_for_purpose('modules') or '' |
There was a problem hiding this comment.
In CI/E2E mode, token/key variables are defaulted from GitHubTokenManager().get_token_for_purpose('modules') regardless of which env var is being requested. This can populate the wrong kind of token (e.g., a GitHub modules PAT into a variable that expects GITHUB_COPILOT_PAT, ADO_APM_PAT, or other service-specific keys). Consider mapping var_name to an appropriate token purpose (or leaving it empty unless it matches known GitHub module token vars) to avoid misconfiguring MCP server env.
| # 1. Per-org env var (any host) | ||
| if org: | ||
| env_name = f"GITHUB_APM_PAT_{_org_to_env_suffix(org)}" | ||
| token = os.environ.get(env_name) | ||
| if token: | ||
| return token, env_name | ||
|
|
There was a problem hiding this comment.
In AuthResolver._resolve_token(), the per-org env var GITHUB_APM_PAT_{ORG} is applied unconditionally for any host, including Azure DevOps. This can cause ADO dependencies to pick up a GitHub PAT (because ADO org is derived from the repo path) even though ADO auth should come only from ADO_APM_PAT, leading to guaranteed auth failures/confusing behavior. Gate the per-org GitHub env var to GitHub-like hosts only (github/ghe_cloud/ghes), or introduce an ADO-specific per-org mechanism if needed, but avoid using GITHUB_APM_PAT_* for host_info.kind == 'ado'.
|
|
||
| def resolve(self, host: str, org: Optional[str] = None) -> AuthContext: | ||
| """Resolve auth for *(host, org)*. Cached & thread-safe.""" | ||
| key = (host, org) |
There was a problem hiding this comment.
AuthResolver.resolve() caches results by the raw (host, org) tuple without normalizing case. Since host classification is case-insensitive, calling resolve('GitHub.COM') vs resolve('github.com') will create duplicate cache entries and can lead to inconsistent behavior in callers that pass differently-cased hosts. Consider normalizing host (and optionally org) for the cache key (e.g., host.lower() and a consistent org normalization) while still preserving the original host string in HostInfo if needed for display.
| key = (host, org) | |
| # Normalize the cache key to avoid case-sensitive duplication. | |
| key_host = host.lower() if host is not None else None | |
| key_org = org.lower() if org is not None else None | |
| key = (key_host, key_org) |
src/apm_cli/commands/install.py
Outdated
| if not validated_packages: | ||
| if dry_run: | ||
| _rich_warning("No new packages to add") | ||
| _rich_warning("No new packages to add") if not logger else None |
There was a problem hiding this comment.
When logger is provided and dry_run is true, the not validated_packages branch suppresses all output (... if not logger else None). That makes apm install --dry-run <pkg> silent in the "nothing new to add" case. Use the logger to emit an info/warning (e.g., via logger.progress()/logger.warning()) so users still get feedback in dry-run mode.
| _rich_warning("No new packages to add") if not logger else None | |
| if logger: | |
| logger.progress( | |
| "Dry run: No new packages to add; all requested packages are already in apm.yml" | |
| ) | |
| else: | |
| _rich_warning("No new packages to add") |
src/apm_cli/core/command_logger.py
Outdated
| status = "✓" if success else "✗" | ||
| msg = f" auth: {status} {step}" | ||
| if detail: | ||
| msg += f" ({detail})" | ||
| _rich_echo(msg, color="dim") |
There was a problem hiding this comment.
CommandLogger.auth_step() hardcodes Unicode status glyphs ("✓"/"✗") into the message string. This conflicts with the codebase’s ASCII-safe symbol approach (STATUS_SYMBOLS via the symbol= parameter) and can render poorly on Windows/codepages. Prefer using symbol="check"/symbol="error" (or similar) and keep the message text free of inline glyphs.
This issue also appears on line 164 of the same file.
| status = "✓" if success else "✗" | |
| msg = f" auth: {status} {step}" | |
| if detail: | |
| msg += f" ({detail})" | |
| _rich_echo(msg, color="dim") | |
| msg = f" auth: {step}" | |
| if detail: | |
| msg += f" ({detail})" | |
| symbol = "check" if success else "error" | |
| _rich_echo(msg, color="dim", symbol=symbol) |
_validate_package_exists was referencing _rich_echo which was not imported, causing a silent NameError caught by the outer try/except. Added _rich_echo import and wired verbose_callback through both try_with_fallback call sites so --verbose shows the full auth resolution chain during package validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verbose output now shows: - Auth resolution summary (host, org, token source, token type) - Sanitized git stderr on each failed attempt (no token leaked) - Full fallback chain visibility: unauth → PAT → credential fill This gives users actionable diagnostics when auth fails. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fine-grained PATs (github_pat_) get 403 when using the
x-access-token:{token}@host URL format because GitHub rejects Basic
auth for these tokens. Switch validation to use:
git -c http.extraHeader='Authorization: Bearer {token}' ls-remote
This works with ALL token types (fine-grained, classic, OAuth, GitHub
App). The x-access-token URL format only works reliably with classic
PATs (ghp_) and GitHub App installation tokens (ghs_).
Note: the broader clone path (_build_repo_url / build_https_clone_url)
still uses x-access-token — tracked for a follow-up fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
git ls-remote with http.extraHeader has a fatal flaw: the Bearer
header persists across credential-helper retries, preventing git from
using credentials from 'gh auth' or OS keychain when the env-var
token fails.
Switch to GitHub REST API (GET /repos/{owner}/{repo}) which:
- Works with ALL token types (fine-grained, classic, OAuth, App)
- Returns clean HTTP status codes (200=ok, 404=no access, 401=bad token)
- No credential-helper interference
- Faster than spawning git subprocess
The git ls-remote approach remains in the ADO/GHES path where the
API endpoint may differ.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fine-grained PATs are scoped to a single resource owner (user or org). A user-scoped PAT cannot access org repos — even for internal repos where the user is a member. Document required permissions (Metadata Read + Contents Read), resource owner setup, and alternatives. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Wire build_error_context() into validation failure path so verbose mode shows actionable auth guidance (env vars, token setup) - Add '--verbose for auth details' hint when validation fails without verbose mode - Add ADO guard to _try_credential_fallback() preventing credential fill for Azure DevOps hosts (consistent with _resolve_token policy) - Add verbose logging to git ls-remote validation path (ADO/GHE/GHES) matching the GitHub API path's diagnostic output - Add '--verbose for detailed diagnostics' hint to top-level install error handlers - Fix traffic-light violations: orphan removal failure → error (was warning), 'no new packages' → info (was warning), hash mismatch re-download → info/progress (was warning, auto-recovery) - Add 3 new tests for validation failure reason messages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Auth acceptance: - scripts/test-auth-acceptance.sh: 10 P0 auth scenarios covering public/private repos, token priority, fallback chain, error paths. Runnable locally (set tokens + run) or via CI workflow_dispatch. - .github/workflows/auth-acceptance.yml: manual trigger with inputs for test repos, uses auth-acceptance environment for secrets. Logging acceptance: - tests/acceptance/test_logging_acceptance.py: 16 tests verifying install output contract (validation, errors, dry-run, verbose, symbol consistency, diagnostics ordering) — fully mocked, no network needed. Moderate UX fixes: - Route hash mismatch, orphan removal, and lockfile errors through DiagnosticCollector for deferred summary rendering - Fix compile/cli.py color bypasses: _rich_echo(color=yellow/red) replaced with logger.warning()/logger.error() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add scenarios for: GH_TOKEN fallback, EMU internal repos, credential helper only (gh auth), token type detection, mixed manifest (public + private), ADO with/without PAT, fine-grained PAT wrong resource owner. Update workflow with GHE/ADO inputs and secrets. Fix emoji symbols to ASCII-safe [+]/[x]/[-] matching CLI convention. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Complete rewrite with:
- Top-level scenario matrix documenting all 18 scenarios across 4
dimensions (sources A1-A7, hosts H1/H2/H4, repos V1-V3, tokens T1-T5)
- Per-function docblock explaining the auth dimension, expected behavior,
and WHY the assertion matters
- Full local usage guide covering ALL env vars: GITHUB_APM_PAT,
GITHUB_APM_PAT_{ORG}, GITHUB_TOKEN, GH_TOKEN, ADO_APM_PAT
- All repo types: public, private, EMU internal, ADO
- ASCII-safe symbols matching CLI conventions
- Auto-skip with clear reason when required env vars are missing
- Startup banner showing which tokens and repos are configured
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use tee + PIPESTATUS to stream APM install output to stdout in real-time while capturing it for assertions. Enables live debugging in CI/CD environments — no need to wait for scenario completion to see what happened. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add two new E2E auth scenarios that test the hardest real-world case: a single apm.yml mixing dependencies from multiple auth domains. Scenario 19 (mega-manifest): builds one manifest with all configured repos (public, private, EMU, ADO, second-org) and verifies every dep installs when all tokens are set simultaneously. Scenario 20 (multi-org per-org PAT routing): two private repos from different orgs with ONLY per-org PATs — no global GITHUB_APM_PAT. Validates the resolver routes each dep to its own per-org token. Also fixes: - Replace bash 3.2-incompatible `declare -A` with indexed arrays (macOS portability) - Fix `set -e` bug in run_install that enabled errexit and caused the script to exit on first assertion failure Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite scenario 19 as a truly brutal chaos mega-manifest that mixes every dependency format and auth source in a single apm.yml install: public shorthand, public FQDN, private org A, private org B, EMU internal, and ADO — all in one pass. The resolver must route each dep to its correct token independently. Add --mega flag to run ONLY the chaos manifest (vs progressive mode which runs all 20 scenarios). Usage: set -a && source .env && set +a ./scripts/test-auth-acceptance.sh # progressive (all 20) ./scripts/test-auth-acceptance.sh --mega # chaos mega only Create .env (gitignored) with documented token placeholders and usage instructions for running the test suite locally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add slots 7 and 8 to the mega-manifest for YAML dict format deps:
- Slot 7: private repo via { git: https://...git } (AUTH_TEST_GIT_URL_REPO)
- Slot 8: public repo via { git: https://...git } (AUTH_TEST_GIT_URL_PUBLIC_REPO)
These test parse_from_dict() — a completely different parser path than
string shorthand or FQDN. Auth resolves from the URL's host+org.
Both slots use separate env vars to avoid unique-key dedup with the
string shorthand slots. Slot 7 falls back to PRIVATE_REPO_2 if unset.
Also updates .env template with the new repo vars.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a transitive dependency fails to download (e.g., auth failure for
an unknown org), error messages now include the full dependency chain
that led to the failure:
Failed to resolve transitive dep other-org/leaf-pkg
(via acme/root-pkg > other-org/leaf-pkg): 401 Unauthorized
Architecture (clean SoC):
- Resolver (_build_parent_chain): computes breadcrumb by walking up
DependencyNode.parent links — it owns the tree structure
- Callback (install.py): receives chain as optional 3rd arg, uses it
for verbose logging and diagnostics — it owns the output
- DownloadCallback type: changed to Callable[...] to accept the new
parent_chain parameter without breaking existing signatures
Transitive failures are now:
1. Logged inline via logger.verbose_detail() (verbose mode)
2. Collected into DiagnosticCollector for the deferred summary (always)
Tests:
- 3 unit tests for _build_parent_chain (3-level, single, None)
- 1 integration test: resolver with callback tracking verifies the
chain string contains parent dep name and '>' separator
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| assert result is False | ||
| mock_build_ctx.assert_called_once() | ||
| call_args = mock_build_ctx.call_args | ||
| assert "github.com" in call_args[0][0] # host |
Check failure
Code scanning / CodeQL
Incomplete URL substring sanitization High test
Copilot Autofix
AI about 3 hours ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
…dedup auth, traffic-light fixes - Replace Callable[...] DownloadCallback with typed Protocol class - Move _build_parent_chain() to DependencyNode.get_ancestor_chain() - Remove unused _resolution_path field from resolver - Deduplicate AuthResolver instantiation in _validate_package_exists - Fix traffic-light: 'no deps' warning→info, lockfile failure warning→error - All 2874 tests passing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ebase Wave 1 (verbose coverage): - Add dep tree resolution summary with transitive breadcrumbs - Add auth source/type logging in download phase (verbose only) - Add manifest parsing + per-dep lockfile SHA in verbose output - Add download URL verbose logging via verbose_callback in downloader Wave 2 (CommandLogger migration): - compile/watcher.py: 19 _rich_* calls → CommandLogger - uninstall/engine.py: 15 _rich_* calls → CommandLogger, remove unicode symbols - safe_installer.py: 6 calls with logger fallback pattern - _helpers.py, packer.py, plugin_exporter.py: logger threading - audit.py already migrated (verified) All 2874 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
install.py: - Remove 30 dead else-branch _rich_* fallbacks (logger always available) - Remove 3 duplicate _rich_* calls alongside logger calls - Remove unused _rich_info import; clean test mocks MCPIntegrator: - Add diagnostics param to collect_transitive() and install() - Thread diagnostics from install.py; transitive trust warning uses diagnostics skill_integrator: normalization warning already routes through diagnostics ✅ All 2874 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- command_logger.py: ✓→[+], ✗→[x], —→-- - diagnostics.py: ⚠→[!], ✗→[x], —→-- - install.py: ✓→[+], →→->, —→-- - Updated test assertions to match new ASCII symbols All 2874 tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comprehensive handbook for AI Engineers using GitHub Copilot CLI to ship large multi-concern changes via agent fleets. - Three-actor model: User (strategist), Harness (Copilot CLI), Agents (fleet) - Full meta-process: Audit, Plan, Wave Execution, Validate, Ship - Repository instrumentation guide (agents, skills, instructions) - Wave execution model with dependency mapping and parallelization - Test ring pipeline (unit, acceptance, integration) - Escalation protocol and feedback loop for primitive improvement - Autonomous CI/CD patterns for drift detection - Live Dashboard POC via Copilot CLI Hooks (Appendix D) - Prompt examples written from the user's perspective Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Auth + Logging Architecture Overhaul
Closes #393 and #327. Subsumes #389 (credential timeout fix — credit to @frblondin).
Problem
git ls-remoteran without auth.Solution
AuthResolver (
src/apm_cli/core/auth.py)try_with_fallback()retries withgit credential fillautomaticallytry_with_fallback(): unauth-first for validation (saves rate limits), auth-first for downloads, credential-fill fallback on failureCommandLogger (
src/apm_cli/core/command_logger.py)_rich_*helpersInstallLoggersubclass with validation/resolution/download phasesAuth Flow
flowchart TD A[Dependency Reference] --> B{Per-org env var?} B -->|GITHUB_APM_PAT_ORG| C[Use per-org token] B -->|Not set| D{Global env var?} D -->|GITHUB_APM_PAT / GITHUB_TOKEN / GH_TOKEN| E[Use global token] D -->|Not set| F{Git credential fill?} F -->|Found| G[Use credential] F -->|Not found| H[No token] E --> I{try_with_fallback} C --> I G --> I H --> I I -->|Token works| J[Success] I -->|Token fails| K{Credential-fill fallback} K -->|Found credential| J K -->|No credential| L{Host has public repos?} L -->|Yes| M[Try unauthenticated] L -->|No| N[Auth error with actionable message]Provenance Matrix
org/repo(bare)default_host()github.com/org/repocontoso.ghe.com/org/repoGITHUB_HOSTdev.azure.com/org/proj/repoADO_APM_PATonlyGITHUB_APM_PAT_{ORG}→ global → credential fillToken Prefix Classification
github_pat_ghp_ghu_gh auth login, OAuth appsgho_ghs_ghr_Key Design Decisions
try_with_fallback()retries withgit credential fill. This makesgh auth loginfor multiple hosts work seamlessly.GITHUB_APM_PAT_{ORG}always wins regardless of host.APM_GIT_CREDENTIAL_TIMEOUT, max 180s). Fixes Windows credential picker timeouts.Commits
6366f9bfdc7d086ef2511c355d026ddaea437978acTest Coverage