Skip to content

feat(agentplugins): harden lifecycle and release E2E - #47

Merged
777genius merged 18 commits into
mainfrom
feat/agentplugins-p0-lifecycle
Aug 9, 2026
Merged

feat(agentplugins): harden lifecycle and release E2E#47
777genius merged 18 commits into
mainfrom
feat/agentplugins-p0-lifecycle

Conversation

@777genius

@777genius 777genius commented Aug 9, 2026

Copy link
Copy Markdown
Owner

What

  • make repeated add/update resume incomplete activation and OAuth flows instead of dead-ending
  • preserve separate verified, attested, manual, and auth-pending lifecycle evidence
  • add strict client verification for Codex, Copilot CLI, and Kiro without trusting ambiguous output
  • make doctor and repair installation-scoped, transactional, and safe under concurrent state changes
  • validate catalog compatibility/auth metadata instead of discarding it
  • gate releases with native runtime checks for all six distributed OS/architecture binaries
  • harden npm bootstrap, release manifests, checksums, tags, and draft/prerelease handling

Why

The existing Agent Plugins 1.0 package flow could materialize packages, but several manual activation, OAuth, verification, and release paths were stronger in documentation than in runtime evidence. This closes those launch blockers while preserving plugin.json as the standard-first install contract.

Safety

All agent/client behavior is covered through isolated fixtures and fake executables. No real user project, client configuration, OAuth session, or installed agent was mutated.

Validation

  • full Go workspace: go test ./...
  • focused race tests for providers, lifecycle, and CLI
  • Agent Plugins repository contract tests
  • npm launcher/bootstrap suite: 18/18
  • release workflow/schema parsing and diff checks
  • independent final xhigh review: no P0-P2 findings

Summary by CodeRabbit

  • New Features

    • Added a repair command with verification, dry-run, confirmation, and safe restoration options.
    • Added lifecycle resumption, activation and authentication tracking, and clearer next-step guidance.
    • Improved client detection across macOS, Linux, and Windows, including Kiro and VS Code installations.
    • Added diagnostic reporting for installation integrity, compatibility, and recovery issues.
  • Release Improvements

    • Releases now require verified six-platform validation before publication.
    • Strengthened asset, checksum, manifest, provenance, and lifecycle verification.
    • npm packages now support only macOS, Linux, and Windows on x64 or arm64 systems.

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@777genius, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b71459db-c3ae-4440-92ed-1dbfa7fb425c

📥 Commits

Reviewing files that changed from the base of the PR and between cf42065 and 99c26a6.

📒 Files selected for processing (1)
  • cli/plugin-kit-ai/internal/agentpluginscli/cli_test.go
📝 Walkthrough

Walkthrough

The pull request adds immutable release and publication gates, six-platform runtime proofs, stricter catalog and client verification, lifecycle resumption, transactional repair, and expanded CLI diagnostics and tests.

Changes

Release validation and publication

Layer / File(s) Summary
Release assets and platform proof
.github/workflows/agentplugins-platform-proof.yml, npm/agentplugins/scripts/*, npm/agentplugins/test/*, npm/agentplugins/package.json
Release assets now include schema and commit metadata, checksums, exact platform files, and legacy-manifest handling. Native proofs install and validate the exact tarball in isolated environments.
Draft release gating and publication
.github/workflows/agentplugins-release.yml, .github/workflows/agentplugins-npm-publish.yml, docs/agentplugins-release.md, repotests/agentplugins_release_contract_test.go
Releases now use immutable drafts, six native proofs, attestation checks, exact tag and commit validation, and promotion only after proof completion. npm publication uses the tested tarball directly.
Registry lifecycle smoke validation
.github/workflows/agentplugins-npm-publish.yml
The registry smoke test uses an isolated synthetic plugin and checks activation attestations, lifecycle commands, no-op updates, removal, and path isolation.

Catalog and installer behavior

Layer / File(s) Summary
Catalog evidence and client detection
install/integrationctl/agentplugins/domain/*, install/integrationctl/agentplugins/adapters/catalog/*, install/integrationctl/agentplugins/adapters/clientdetect/*, install/integrationctl/agentplugins/adapters/loader/loader.go, install/integrationctl/agentplugins/adapters/statev2/store_test.go, install/integrationctl/agentplugins/source.go
Catalog resolutions now preserve immutable evidence and a fixed five-client compatibility matrix. Detection covers Windows and Linux installation surfaces, including legacy Kiro CLI evidence.
Planning and verification state
install/integrationctl/agentplugins/planner/*, install/integrationctl/agentplugins/ports/interfaces.go, install/integrationctl/agentplugins/providers/stager.go
Planning uses affirmative catalog evidence for authentication and package compatibility. Verification failures use typed classifications with digest details and preserved underlying errors.
Lifecycle convergence and repair
install/integrationctl/agentplugins/usecase/*, install/integrationctl/agentplugins/providers/activator*
Lifecycle operations support read-only verification, resumable activation, explicit activation and authentication attestations, authoritative observations, strict client output parsing, and transactional repair from recorded sources.
CLI commands and diagnostics
cli/plugin-kit-ai/internal/agentpluginscli/*
The CLI adds repair, expands doctor findings, supports lifecycle resumption, renders verification and authentication status, and blocks unsafe or unverifiable mutations. Tests cover recovery, repair, lifecycle, verifier, and input behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant ReleaseWorkflow
  participant PlatformProof
  participant NativeRuntimes
  participant NpmPublish
  ReleaseWorkflow->>PlatformProof: stage and verify the immutable draft
  PlatformProof->>NativeRuntimes: run six native runtime proofs
  NativeRuntimes-->>PlatformProof: return proof artifacts
  PlatformProof-->>ReleaseWorkflow: return gate eligibility and tested tarball
  ReleaseWorkflow->>NpmPublish: pass the verified release identity and tarball
  NpmPublish-->>ReleaseWorkflow: publish the exact tested tarball
Loading

Poem

A rabbit checks each tag and file,
Six platforms hop in single file.
Attestations bloom, drafts wait,
Repair restores what changed its state.
The CLI guides each lifecycle trail—
With carrots packed, the proofs prevail.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes to Agent Plugins lifecycle handling and release end-to-end validation.
Description check ✅ Passed The description explains what changed, why it changed, safety constraints, and validation results, including release and test coverage.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agentplugins-p0-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
install/integrationctl/agentplugins/planner/planner.go (1)

100-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not append activation guidance to an unsupported plan.

applyCatalogCompatibility can set plan.Status = domain.PlanUnsupported and plan.Activation = domain.ActivationFailed at Line 130 and Line 146, and the function does not return early. The switch at Lines 100-119 then still appends client guidance such as "finish installation in Codex or ChatGPT Plugins, then start a new session". The user receives both "choose a client present in the pinned catalog evidence" and instructions to finish an installation that the planner refused. The same happens for the no_valid_components and no_supported_components paths at Lines 86-94.

Skip the client-specific guidance when the plan is unsupported.

🛠️ Proposed fix
-	switch client.ClientID {
+	if plan.Status == domain.PlanUnsupported {
+		return plan, nil
+	}
+	switch client.ClientID {
 	case domain.ClientCodex:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/planner/planner.go` around lines 100 -
119, Skip the client-specific guidance switch when the plan has status
domain.PlanUnsupported, including plans made unsupported by no_valid_components,
no_supported_components, or applyCatalogCompatibility. Ensure unsupported plans
retain only their existing unsupported-plan messaging and do not append
activation instructions.
🧹 Nitpick comments (18)
npm/agentplugins/test/stage-release.test.js (1)

86-110: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider covering the rejected legacy manifest shape.

verifyRelease also throws legacy release manifest has unexpected fields when a schema-1 manifest carries extra keys. That branch is currently untested. Add one case that writes a schema-1 manifest with an extra key and asserts the throw, so the audit path stays strict.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@npm/agentplugins/test/stage-release.test.js` around lines 86 - 110, Extend
the legacy manifest test around verifyRelease with a schema-1 manifest
containing an unexpected extra field, then assert that the allowLegacyManifest
audit path throws the “legacy release manifest has unexpected fields” error.
Keep the existing valid schema-1 audit assertions unchanged.
.github/workflows/agentplugins-platform-proof.yml (1)

244-252: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Bind workflow expressions through env in every run block. Four steps expand ${{ ... }} directly into shell script bodies. Most of these steps already declare an env: block for other values, so the pattern is inconsistent. Binding each value to an environment variable keeps the script text static, removes the shell-quoting dependency on the expression value, and clears the zizmor template-injection findings.

  • .github/workflows/agentplugins-platform-proof.yml#L244-L252: bind needs.prepare.outputs.tarball_file, needs.prepare.outputs.version, matrix.target, matrix.lifecycle, and runner.temp to env and reference them as shell variables in the platform-proof.js invocation.
  • .github/workflows/agentplugins-release.yml#L229-L250: add FROZEN_COMMIT: ${{ needs.validate.outputs.commit }} to the existing env block and use --target "${FROZEN_COMMIT}".
  • .github/workflows/agentplugins-npm-publish.yml#L92-L97: add FROZEN_COMMIT and GATE_ELIGIBLE to the existing env block and compare against those variables.
  • .github/workflows/agentplugins-npm-publish.yml#L129-L136: add RELEASE_COMMIT: ${{ steps.publish-gate.outputs.commit }} to the existing env block and use it for both the release-assets.js verify argument and --source-digest.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agentplugins-platform-proof.yml around lines 244 - 252,
Replace direct workflow-expression interpolation in all four sites with
environment-variable bindings: in
.github/workflows/agentplugins-platform-proof.yml lines 244-252, bind the
tarball file, version, target, lifecycle, and runner temp values and reference
shell variables in the platform-proof.js command; in
.github/workflows/agentplugins-release.yml lines 229-250, add FROZEN_COMMIT to
the existing env block and use it for --target; in
.github/workflows/agentplugins-npm-publish.yml lines 92-97, bind FROZEN_COMMIT
and GATE_ELIGIBLE and compare those variables; and in lines 129-136, bind
RELEASE_COMMIT and use it for both release-assets.js verify and --source-digest.

Source: Linters/SAST tools

npm/agentplugins/scripts/platform-proof.js (1)

104-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the installed package directory from the tarball manifest.

Line 104 hardcodes universal-agent-plugins. The publish workflow resolves the package name from package.json and passes it as EXPECTED_PACKAGE. If the distribution name changes, this proof fails with an unclear ENOENT instead of a named identity error. Consider accepting the expected package name as an argument, or reading it from the project package.json dependencies after install.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@npm/agentplugins/scripts/platform-proof.js` around lines 104 - 111, The
platform proof currently hardcodes the installed directory name in the
packageRoot construction. Update the surrounding proof entrypoint to use the
expected package name supplied by the publish workflow, such as EXPECTED_PACKAGE
or an equivalent argument, and construct packageRoot from that value. Preserve
the existing version, script, and manifest validations while ensuring a missing
or mismatched package identity produces the named validation failure rather than
an unclear ENOENT.
.github/workflows/agentplugins-release.yml (1)

281-289: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Assert platform-proof gate eligibility before promotion.

The npm publish workflow checks needs.platform-proof.outputs.gate_eligible at line 95 of agentplugins-npm-publish.yml. This job does not. Today allow_legacy_manifest: false makes a non-eligible outcome impossible, so the gap is latent rather than exploitable. Add the explicit check so the promotion gate does not depend on that indirect argument.

♻️ Proposed change
         env:
           GH_TOKEN: ${{ github.token }}
           TAG: ${{ inputs.tag }}
           FROZEN_COMMIT: ${{ needs.validate.outputs.commit }}
           EXPECTED_ASSET_SET_DIGEST: ${{ needs.stage-draft.outputs.asset_set_digest }}
+          GATE_ELIGIBLE: ${{ needs.platform-proof.outputs.gate_eligible }}
         run: |
+          test "${GATE_ELIGIBLE}" = "true"
+          [[ "${EXPECTED_ASSET_SET_DIGEST}" =~ ^[0-9a-f]{64}$ ]] || {
+            echo "missing frozen asset-set digest from the staging job" >&2
+            exit 1
+          }
           git fetch --force origin "refs/tags/${TAG}:refs/tags/${TAG}"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agentplugins-release.yml around lines 281 - 289, Update
the “Reverify exact draft and promote only after all native proofs” job to
explicitly validate needs.platform-proof.outputs.gate_eligible before promotion,
matching the guard used by the npm publish workflow. Fail the job when the value
is not eligible, while preserving the existing tag and frozen-commit
verification.
install/integrationctl/agentplugins/ports/interfaces.go (1)

27-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider keeping the classification in the message.

When Err is set, Error() returns only the wrapped message and drops Kind. Logs then lose the classification that this type exists to carry. Wrapping the kind keeps errors.Is and errors.As behavior through Unwrap and improves the log text.

♻️ Proposed change
 func (err *VerificationError) Error() string {
 	if err.Err != nil {
-		return err.Err.Error()
+		return fmt.Sprintf("%s: %s", err.Kind, err.Err.Error())
 	}
 	return fmt.Sprintf("package verification failed: %s", err.Kind)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/ports/interfaces.go` around lines 27 -
32, Update VerificationError.Error to include err.Kind in the message even when
err.Err is non-nil, while preserving the wrapped error details and existing
Unwrap behavior so errors.Is and errors.As continue to work.
install/integrationctl/agentplugins/planner/planner.go (1)

81-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a tagged switch on plan.Authentication.

staticcheck reports QF1003 here. A tagged switch also makes new AuthenticationState values easier to add.

♻️ Proposed change
-	if plan.Authentication == domain.AuthenticationPending {
-		plan.UserActions = append(plan.UserActions, "complete authentication for this plugin in the selected client")
-	} else if plan.Authentication == domain.AuthenticationNotChecked {
-		plan.UserActions = append(plan.UserActions, "verify the plugin's authentication requirements before using it")
-	}
+	switch plan.Authentication {
+	case domain.AuthenticationPending:
+		plan.UserActions = append(plan.UserActions, "complete authentication for this plugin in the selected client")
+	case domain.AuthenticationNotChecked:
+		plan.UserActions = append(plan.UserActions, "verify the plugin's authentication requirements before using it")
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/planner/planner.go` around lines 81 - 85,
Replace the if/else chain keyed by plan.Authentication with a tagged switch on
plan.Authentication, preserving the existing UserActions append behavior for
AuthenticationPending and AuthenticationNotChecked and leaving other
authentication states unchanged.

Source: Linters/SAST tools

install/integrationctl/agentplugins/providers/activator.go (2)

428-428: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix the ST1005 lint failure on the error string.

golangci-lint reports ST1005 for the capitalized error string. Lowercase the first word to pass the linter. The same pattern exists at Line 563 and Line 647; align all three.

🔧 Proposed fix
-var errCodexListContractUnknown = errors.New("Codex plugin list output is not recognized")
+var errCodexListContractUnknown = errors.New("codex plugin list output is not recognized")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/providers/activator.go` at line 428,
Lowercase the first word in the error strings for errCodexListContractUnknown
and the corresponding error declarations near the other two reported locations,
preserving the rest of each message so all three satisfy ST1005.

Source: Linters/SAST tools


434-435: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Read the bytes directly.

strings.NewReader(string(body)) copies the whole payload. bytes.NewReader(body) avoids the copy and reads the same content.

♻️ Proposed change
-	decoder := json.NewDecoder(strings.NewReader(string(body)))
+	decoder := json.NewDecoder(bytes.NewReader(body))

Add the bytes import if it is not already present.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/providers/activator.go` around lines 434
- 435, Update the decoder initialization near UseNumber to pass body directly
through bytes.NewReader instead of converting it to a string first, and add the
bytes import if needed. Preserve the existing JSON decoding behavior.
cli/plugin-kit-ai/internal/agentpluginscli/source.go (1)

99-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider one shared clone helper.

cloneCatalogCompatibility duplicates cloneCompatibility in install/integrationctl/agentplugins/adapters/catalog/catalog.go. Both functions have identical behavior. Export one helper from the domain package and call it from both sites. This keeps the copy semantics in one place if CatalogCompatibility gains reference-typed fields later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/plugin-kit-ai/internal/agentpluginscli/source.go` around lines 99 - 108,
Replace the duplicate cloneCatalogCompatibility and cloneCompatibility
implementations with one exported clone helper in the domain package. Update
both source.go and the catalog adapter to call that shared helper, preserving
the current nil-for-empty behavior and shallow-copy semantics.
install/integrationctl/agentplugins/adapters/catalog/catalog_test.go (1)

114-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the hardcoded embedded catalog size assertion.

The test should assert the Load result is non-empty and valid; an exact plugin count makes the test fail on future catalog additions. The matrix invariant is already enforced during Load, including rejection of empty plugin lists.

♻️ Proposed change
-	if len(loaded.Catalog.Plugins) != 26 {
-		t.Fatalf("embedded package count = %d, want 26", len(loaded.Catalog.Plugins))
+	if len(loaded.Catalog.Plugins) == 0 {
+		t.Fatal("embedded catalog contains no plugins")
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/adapters/catalog/catalog_test.go` around
lines 114 - 127, Update TestEmbeddedCatalogAllEntriesUseExactCompatibilityMatrix
to remove the exact len(loaded.Catalog.Plugins) == 26 assertion. Instead, assert
that the Load result is valid and contains a non-empty plugin list, relying on
Loader.Load for compatibility-matrix validation and empty-list rejection.
install/integrationctl/agentplugins/usecase/repair.go (2)

109-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use time.RFC3339Nano instead of the inline layout.

Both lines repeat the literal "2006-01-02T15:04:05.999999999Z07:00". service.updateLifecycle uses time.RFC3339Nano for the same field. Use the constant so all timestamp writers stay identical.

Also applies to: 162-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/usecase/repair.go` at line 109, Update
the timestamp formatting in service.updateLifecycle and the other
corrected.UpdatedAt assignment to use time.RFC3339Nano instead of the repeated
inline layout, keeping all lifecycle timestamp writers consistent.

74-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the inner verifyErr to remove the shadow.

Line 76 declares a second verifyErr inside the verifyErr == nil block. The outer verifyErr is still used at line 120 after the block. The current code is correct, but the shadow makes later edits inside the block risky. Use a distinct name, for example clientErr.

♻️ Proposed rename
-		verified, verifyErr := service.verifyClientReadOnly(ctx, input, result, client)
-		if verifyErr != nil {
-			return result, verifyErr
-		}
+		verified, clientErr := service.verifyClientReadOnly(ctx, input, result, client)
+		if clientErr != nil {
+			return result, clientErr
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/usecase/repair.go` around lines 74 - 79,
Rename the inner error variable returned by service.verifyClientReadOnly in the
verifyErr == nil block to a distinct name such as clientErr, and update its nil
check and return accordingly; preserve the outer verifyErr for later use.
cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go (1)

80-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Explicitly discard the cleanup error.

golangci-lint reports an unchecked error return on line 81. Assign the result to the blank identifier so the deferred call states the intent.

🔧 Proposed fix
 	if loaded.cleanup != nil {
-		defer loaded.cleanup()
+		defer func() { _ = loaded.cleanup() }()
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go` around lines 80 -
82, Update the deferred loaded.cleanup call in the lifecycle cleanup block to
explicitly assign its returned error to the blank identifier, preserving the
existing defer behavior while satisfying unchecked-error linting.

Source: Linters/SAST tools

install/integrationctl/agentplugins/usecase/service_test.go (2)

174-191: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace the map-indexed conditional with a plain if.

map[bool]string{true: "projected", false: "native"}[clientID == domain.ClientCodex] allocates a map to pick one of two strings. A small packageMode := "native" with an if is clearer and matches the rest of the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/usecase/service_test.go` around lines 174
- 191, The test setup in TestOpenAIOAuthHintsDoNotOverrideGenericAuthentication
uses a map-indexed conditional to select the package value. Replace it with a
packageMode variable initialized to "native" and a plain if that assigns
"projected" for ClientCodex, then use packageMode in CatalogEvidence.

85-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The evidence assertions test the input, not the persisted state.

input.Envelope.Manifest.Raw, input.Envelope.CatalogEvidence and input.Envelope.Diagnostics are values the test itself set at lines 61-66. Those three conditions always hold, so they cannot detect evidence loss. Only packageState.SchemaURI and packageState.ManifestDigest read persisted state. Assert against the reloaded state, or against the returned result, for the evidence fields you care about.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/usecase/service_test.go` around lines 85
- 89, Update the assertions in the test around packageState and the returned or
reloaded persisted state so evidence fields are validated from that state rather
than input.Envelope. Replace the self-referential checks on Manifest.Raw,
CatalogEvidence, and Diagnostics with assertions against the persisted result,
while preserving the existing SchemaURI and ManifestDigest checks.
install/integrationctl/agentplugins/usecase/service.go (2)

360-380: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Two different definitions of "a verifier is available" exist for the same clients.

clientVerifierAvailable requires, for domain.ClientKiro, a kiro executable and MCP-server-only supported components. verifyClientReadOnly requires only the kiro executable. The two functions therefore disagree for a Kiro plan that has no components or has unsupported components: resume treats the verifier as unavailable and preserves the recorded lifecycle, while the converged path still calls Activate and can overwrite that lifecycle from the same client. Derive both from one predicate.

Also applies to: 499-511

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/usecase/service.go` around lines 360 -
380, Unify the Kiro verifier-availability logic used by clientVerifierAvailable
and verifyClientReadOnly into one shared predicate. Preserve the Kiro executable
requirement and MCP-server component validation consistently, including the
no-components and unsupported-component cases, so both paths make the same
availability decision before activation or lifecycle preservation.

143-204: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the converged-verification block; the two copies can drift.

Lines 143-172 and 176-204 run the same sequence: lifecycleConverged, verifyClientReadOnly, authoritative persistence, no-change detection, resume. Only the binding source differs (current versus previousClient). Extract one helper that takes the binding and returns (AddResult, error). That keeps the fail-closed persistence rule in a single place.

Also add parentheses in the persistence condition. input.Confirmed || input.PersistAuthoritativeObservations && verified.AuthoritativeObservation reads as three alternatives, but && binds first.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/usecase/service.go` around lines 143 -
204, Extract the duplicated converged-verification flow from the materialized
add and replace paths into one helper that accepts the client binding and
returns (AddResult, error), preserving lifecycle verification,
authoritative-observation persistence, lifecycle updates, no-change handling,
and resume behavior. Replace both inline blocks with calls to this helper for
current and previousClient. In the authoritative persistence condition, add
parentheses around the PersistAuthoritativeObservations and
verified.AuthoritativeObservation conjunction.
install/integrationctl/agentplugins/adapters/statev2/store_test.go (1)

25-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Legacy shape reuses current domain types, so nested fields escape the strict check.

oldInstallationV2 embeds domain.SourceBinding and domain.ClientBinding. These are the current types. DisallowUnknownFields therefore accepts any new field added inside a client binding or source binding. The test then only proves compatibility at the top two levels. Declare frozen local copies of the 0.1.4 ClientBinding and SourceBinding shapes to close that gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/adapters/statev2/store_test.go` around
lines 25 - 34, Replace the domain.SourceBinding and domain.ClientBinding fields
used by oldInstallationV2 with local frozen 0.1.4-compatible copies, including
their nested field types and JSON tags. Update the legacy test fixtures and
unmarshalling path to use these local ClientBinding and SourceBinding
definitions so DisallowUnknownFields validates nested fields against the
historical schema.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/agentplugins-platform-proof.yml:
- Around line 123-126: Update the commit validation around EXPECTED_COMMIT so
workflow_call executions reject an empty value instead of skipping the check;
require a non-empty, valid commit-format value before comparing commit, while
preserving the existing mismatch failure and message.
- Around line 262-268: Update the proof-complete job’s “Confirm native matrix
aggregation” step to download the agentplugins-native-proof-* artifacts and
validate exactly one proof document for each of the six required native targets.
Retain the native-runtime success check, but make the job fail when any target
artifact or document is missing or the matrix produces fewer than six valid
proofs.
- Around line 73-76: Update the workflow permissions to grant contents: write so
the require_draft path can access draft release assets through GITHUB_TOKEN.
Preserve the existing draft checks and release download verification behavior.

In `@cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go`:
- Around line 97-107: Update the unconfirmed branch in the repair lifecycle flow
to preserve the JSON output contract: when opts.format is "json", render the
appropriate JSON result instead of printing the plain-text "No changes made."
message. Keep the existing human-readable output for other formats and return
without applying changes, matching the behavior implemented by runUpdate.
- Around line 47-96: Use a single consistent stdin reader across
selectBoundClient and the later repair confirmation in runRepair, rather than
creating separate buffered readers that can consume each other’s input. Pass
ownership of one reader through the interactive selection and promptYesNo flow,
or make the chooser use the same unbuffered line-reading mechanism, while
preserving existing non-interactive and dry-run behavior.

In `@cli/plugin-kit-ai/internal/agentpluginscli/read.go`:
- Around line 236-242: Update the Copilot-specific check in the binding findings
logic to require visible before evaluating client.ExecutablePath. Keep
copilot_cli_missing for detected clients with an empty executable path, while
preserving client_not_visible for bindings absent from detectedByID or otherwise
not visible.

In `@install/integrationctl/agentplugins/adapters/clientdetect/detector.go`:
- Around line 41-45: Update the LinuxApplicationDirs construction to include the
user-local applications path only when homeDir is non-blank; otherwise retain
only the system application directories and avoid probing a relative
.local/share/applications path. Use the existing compactPaths setup and nearby
detector logic without changing system paths.

In `@npm/agentplugins/scripts/platform-proof.js`:
- Around line 40-43: Update the argument validation around lifecycleArg in the
platform-proof.js entrypoint to accept only the exact literals "true" or
"false"; reject any other value through fail(...) before the lifecycle proof
logic interprets it, while preserving the existing required-argument checks and
valid-value behavior.
- Around line 97-103: Quote the tarball argument in the npm install invocation
when running on Windows with shell enabled, so paths containing spaces remain a
single argument; preserve the unquoted path for non-Windows execution. Update
the argument construction around tarball and the existing run call.

---

Outside diff comments:
In `@install/integrationctl/agentplugins/planner/planner.go`:
- Around line 100-119: Skip the client-specific guidance switch when the plan
has status domain.PlanUnsupported, including plans made unsupported by
no_valid_components, no_supported_components, or applyCatalogCompatibility.
Ensure unsupported plans retain only their existing unsupported-plan messaging
and do not append activation instructions.

---

Nitpick comments:
In @.github/workflows/agentplugins-platform-proof.yml:
- Around line 244-252: Replace direct workflow-expression interpolation in all
four sites with environment-variable bindings: in
.github/workflows/agentplugins-platform-proof.yml lines 244-252, bind the
tarball file, version, target, lifecycle, and runner temp values and reference
shell variables in the platform-proof.js command; in
.github/workflows/agentplugins-release.yml lines 229-250, add FROZEN_COMMIT to
the existing env block and use it for --target; in
.github/workflows/agentplugins-npm-publish.yml lines 92-97, bind FROZEN_COMMIT
and GATE_ELIGIBLE and compare those variables; and in lines 129-136, bind
RELEASE_COMMIT and use it for both release-assets.js verify and --source-digest.

In @.github/workflows/agentplugins-release.yml:
- Around line 281-289: Update the “Reverify exact draft and promote only after
all native proofs” job to explicitly validate
needs.platform-proof.outputs.gate_eligible before promotion, matching the guard
used by the npm publish workflow. Fail the job when the value is not eligible,
while preserving the existing tag and frozen-commit verification.

In `@cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go`:
- Around line 80-82: Update the deferred loaded.cleanup call in the lifecycle
cleanup block to explicitly assign its returned error to the blank identifier,
preserving the existing defer behavior while satisfying unchecked-error linting.

In `@cli/plugin-kit-ai/internal/agentpluginscli/source.go`:
- Around line 99-108: Replace the duplicate cloneCatalogCompatibility and
cloneCompatibility implementations with one exported clone helper in the domain
package. Update both source.go and the catalog adapter to call that shared
helper, preserving the current nil-for-empty behavior and shallow-copy
semantics.

In `@install/integrationctl/agentplugins/adapters/catalog/catalog_test.go`:
- Around line 114-127: Update
TestEmbeddedCatalogAllEntriesUseExactCompatibilityMatrix to remove the exact
len(loaded.Catalog.Plugins) == 26 assertion. Instead, assert that the Load
result is valid and contains a non-empty plugin list, relying on Loader.Load for
compatibility-matrix validation and empty-list rejection.

In `@install/integrationctl/agentplugins/adapters/statev2/store_test.go`:
- Around line 25-34: Replace the domain.SourceBinding and domain.ClientBinding
fields used by oldInstallationV2 with local frozen 0.1.4-compatible copies,
including their nested field types and JSON tags. Update the legacy test
fixtures and unmarshalling path to use these local ClientBinding and
SourceBinding definitions so DisallowUnknownFields validates nested fields
against the historical schema.

In `@install/integrationctl/agentplugins/planner/planner.go`:
- Around line 81-85: Replace the if/else chain keyed by plan.Authentication with
a tagged switch on plan.Authentication, preserving the existing UserActions
append behavior for AuthenticationPending and AuthenticationNotChecked and
leaving other authentication states unchanged.

In `@install/integrationctl/agentplugins/ports/interfaces.go`:
- Around line 27-32: Update VerificationError.Error to include err.Kind in the
message even when err.Err is non-nil, while preserving the wrapped error details
and existing Unwrap behavior so errors.Is and errors.As continue to work.

In `@install/integrationctl/agentplugins/providers/activator.go`:
- Line 428: Lowercase the first word in the error strings for
errCodexListContractUnknown and the corresponding error declarations near the
other two reported locations, preserving the rest of each message so all three
satisfy ST1005.
- Around line 434-435: Update the decoder initialization near UseNumber to pass
body directly through bytes.NewReader instead of converting it to a string
first, and add the bytes import if needed. Preserve the existing JSON decoding
behavior.

In `@install/integrationctl/agentplugins/usecase/repair.go`:
- Line 109: Update the timestamp formatting in service.updateLifecycle and the
other corrected.UpdatedAt assignment to use time.RFC3339Nano instead of the
repeated inline layout, keeping all lifecycle timestamp writers consistent.
- Around line 74-79: Rename the inner error variable returned by
service.verifyClientReadOnly in the verifyErr == nil block to a distinct name
such as clientErr, and update its nil check and return accordingly; preserve the
outer verifyErr for later use.

In `@install/integrationctl/agentplugins/usecase/service_test.go`:
- Around line 174-191: The test setup in
TestOpenAIOAuthHintsDoNotOverrideGenericAuthentication uses a map-indexed
conditional to select the package value. Replace it with a packageMode variable
initialized to "native" and a plain if that assigns "projected" for ClientCodex,
then use packageMode in CatalogEvidence.
- Around line 85-89: Update the assertions in the test around packageState and
the returned or reloaded persisted state so evidence fields are validated from
that state rather than input.Envelope. Replace the self-referential checks on
Manifest.Raw, CatalogEvidence, and Diagnostics with assertions against the
persisted result, while preserving the existing SchemaURI and ManifestDigest
checks.

In `@install/integrationctl/agentplugins/usecase/service.go`:
- Around line 360-380: Unify the Kiro verifier-availability logic used by
clientVerifierAvailable and verifyClientReadOnly into one shared predicate.
Preserve the Kiro executable requirement and MCP-server component validation
consistently, including the no-components and unsupported-component cases, so
both paths make the same availability decision before activation or lifecycle
preservation.
- Around line 143-204: Extract the duplicated converged-verification flow from
the materialized add and replace paths into one helper that accepts the client
binding and returns (AddResult, error), preserving lifecycle verification,
authoritative-observation persistence, lifecycle updates, no-change handling,
and resume behavior. Replace both inline blocks with calls to this helper for
current and previousClient. In the authoritative persistence condition, add
parentheses around the PersistAuthoritativeObservations and
verified.AuthoritativeObservation conjunction.

In `@npm/agentplugins/scripts/platform-proof.js`:
- Around line 104-111: The platform proof currently hardcodes the installed
directory name in the packageRoot construction. Update the surrounding proof
entrypoint to use the expected package name supplied by the publish workflow,
such as EXPECTED_PACKAGE or an equivalent argument, and construct packageRoot
from that value. Preserve the existing version, script, and manifest validations
while ensuring a missing or mismatched package identity produces the named
validation failure rather than an unclear ENOENT.

In `@npm/agentplugins/test/stage-release.test.js`:
- Around line 86-110: Extend the legacy manifest test around verifyRelease with
a schema-1 manifest containing an unexpected extra field, then assert that the
allowLegacyManifest audit path throws the “legacy release manifest has
unexpected fields” error. Keep the existing valid schema-1 audit assertions
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ad5361f3-c5cb-4233-85b4-13f55c6324e5

📥 Commits

Reviewing files that changed from the base of the PR and between 153989a and 24d9f78.

📒 Files selected for processing (35)
  • .github/workflows/agentplugins-npm-publish.yml
  • .github/workflows/agentplugins-platform-proof.yml
  • .github/workflows/agentplugins-release.yml
  • cli/plugin-kit-ai/internal/agentpluginscli/add.go
  • cli/plugin-kit-ai/internal/agentpluginscli/cli_test.go
  • cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go
  • cli/plugin-kit-ai/internal/agentpluginscli/read.go
  • cli/plugin-kit-ai/internal/agentpluginscli/root.go
  • cli/plugin-kit-ai/internal/agentpluginscli/source.go
  • docs/agentplugins-release.md
  • install/integrationctl/agentplugins/adapters/catalog/catalog.go
  • install/integrationctl/agentplugins/adapters/catalog/catalog_test.go
  • install/integrationctl/agentplugins/adapters/clientdetect/detector.go
  • install/integrationctl/agentplugins/adapters/clientdetect/detector_test.go
  • install/integrationctl/agentplugins/adapters/loader/loader.go
  • install/integrationctl/agentplugins/adapters/statev2/store_test.go
  • install/integrationctl/agentplugins/domain/catalog.go
  • install/integrationctl/agentplugins/domain/clients.go
  • install/integrationctl/agentplugins/domain/types.go
  • install/integrationctl/agentplugins/planner/planner.go
  • install/integrationctl/agentplugins/planner/planner_test.go
  • install/integrationctl/agentplugins/ports/interfaces.go
  • install/integrationctl/agentplugins/providers/activator.go
  • install/integrationctl/agentplugins/providers/activator_test.go
  • install/integrationctl/agentplugins/providers/stager.go
  • install/integrationctl/agentplugins/usecase/repair.go
  • install/integrationctl/agentplugins/usecase/service.go
  • install/integrationctl/agentplugins/usecase/service_test.go
  • npm/agentplugins/package.json
  • npm/agentplugins/scripts/platform-proof.js
  • npm/agentplugins/scripts/release-assets.js
  • npm/agentplugins/scripts/stage-release.js
  • npm/agentplugins/test/bootstrap.test.js
  • npm/agentplugins/test/stage-release.test.js
  • repotests/agentplugins_release_contract_test.go

Comment on lines +73 to +76
permissions:
contents: read
attestations: read

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does GITHUB_TOKEN with contents read permission allow downloading draft release assets

💡 Result:

No, a GITHUB_TOKEN with only contents: read permission does not allow you to download assets from a draft release [1][2]. GitHub treats draft releases as non-public, "unpublished" resources [3]. To make draft releases visible and accessible to the GITHUB_TOKEN, the token must have contents: write permission [1][2][3]. If you attempt to use the GitHub CLI (gh) or API to download assets from a draft release with only contents: read, you will typically receive a 404 Not Found error (indicating the release or asset could not be found) [1][2][4] or a 403 Forbidden error because the integration does not have the necessary privilege to see the draft [1]. Even though the operation is technically a "read" action, the elevated contents: write permission is required to bypass the visibility restrictions applied to draft releases [1][2][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target workflow excerpt =="
sed -n '1,200p' .github/workflows/agentplugins-platform-proof.yml | nl -ba | sed -n '1,200p'

echo
echo "== release-related commands in workflow =="
rg -n "release|attestations|contents:|require_draft|expected_asset_set_digest|attestation verify|release download|repository\\.release" .github/workflows/agentplugins-platform-proof.yml

Repository: 777genius/plugin-kit-ai

Length of output: 231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target workflow excerpts =="
awk '{printf "%6d\t%s\n", NR, $0}' .github/workflows/agentplugins-platform-proof.yml | sed -n '1,200p'

echo
echo "== release-related commands and permissions in workflow =="
grep -nE "release|attestations:|contents:|require_draft|attestation verify|release download|repository\.release|expected_asset_set_digest" .github/workflows/agentplugins-platform-proof.yml || true

Repository: 777genius/plugin-kit-ai

Length of output: 14391


🌐 Web query:

GitHub GraphQL Repository release draft GITHUB_TOKEN contents read permissions

💡 Result:

For a GitHub Actions workflow using the auto-generated GITHUB_TOKEN, the permissions model is controlled by the workflow/job-level permissions you set with the permissions: key, and the effective access is scoped to the repository for that job.[1][2][3] 1) Reading “draft releases” via API / GraphQL - The GitHub GraphQL “Release” type includes an isDraft field, so draft releases are represented as Release objects in GraphQL.[4] - GitHub’s Actions documentation emphasizes using permissions: to grant the minimum required access for the GITHUB_TOKEN.[5][2][3] - Practical evidence from GitHub Actions indicates that limiting the token to contents: read may be insufficient for operations involving draft releases; workflows creating/listing drafts often need contents: write.[6][7] 2) Creating/updating a draft release - A GitHub Action that creates a draft release requires the GITHUB_TOKEN to have contents: write permission.[6] 3) How to set it in your workflow Example (job-level): permissions: contents: write GitHub documents that you can set permissions at workflow or job scope to adjust the default permissions granted to GITHUB_TOKEN, and that write includes read.[2][3] 4) Important distinction: GraphQL token vs GITHUB_TOKEN - GitHub’s GraphQL docs describe authenticating to the GraphQL API using PATs or GitHub App/OAuth tokens (which must have the required permissions/scopes for the requested resources).[8] - In Actions, GITHUB_TOKEN is an installation access token created for the workflow job and expires when the job finishes; its permissions are limited to the repository containing the workflow.[1] If you paste your exact GraphQL query (or REST endpoint) and whether you’re only listing drafts vs creating/updating them, I can map it more precisely to the minimum permissions: settings.

Citations:


🌐 Web query:

GitHub CLI gh release download draft release GITHUB_TOKEN contents read write

💡 Result:

To download a draft release using the GitHub CLI (gh), you must be authenticated with a token that has sufficient permissions, specifically contents: write [1][2]. Key considerations for using gh with draft releases: 1. Permission Requirements: While contents: read is often sufficient for public/published releases, draft releases are considered private and restricted [2][3]. To access them via the GitHub CLI or API, the authentication token (such as the default GITHUB_TOKEN in GitHub Actions) must be granted contents: write permissions [1][2]. Without this, you may encounter 403 (Resource not accessible by integration) or 404 errors [2][3]. 2. Authentication: The GitHub CLI requires authentication to interact with private repository data or draft releases [4][5]. If running in GitHub Actions, you can configure the job permissions to grant the necessary access: permissions: contents: write 3. CLI Behavior: Note that there have been known issues where gh release download may fail or behave unexpectedly when targeting a draft release because the expected archive URLs (like zipball_url) are null for drafts [6]. Ensure you are using a recent version of the GitHub CLI to benefit from bug fixes related to draft release handling [6][5]. 4. Accessing Drafts: Draft releases are only visible to users (or integrations) with push access to the repository [3]. If you are using the GITHUB_TOKEN in a workflow, ensure the permissions block is defined at the job or workflow level as shown above [1][7][8].

Citations:


Grant draft-release access instead of requiring both draft checks and downloadable assets.

When require_draft is true, the workflow still runs gh release download "${TAG}" on a draft release. contents: read cannot authenticate draft release assets with the workflow GITHUB_TOKEN; contents: write is required for draft release access. If the gate must stay read-only, remove the draft asset download/verification path when the release is kept as a draft.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agentplugins-platform-proof.yml around lines 73 - 76,
Update the workflow permissions to grant contents: write so the require_draft
path can access draft release assets through GITHUB_TOKEN. Preserve the existing
draft checks and release download verification behavior.

Comment thread .github/workflows/agentplugins-platform-proof.yml Outdated
Comment thread .github/workflows/agentplugins-platform-proof.yml Outdated
Comment thread cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go
Comment thread cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go Outdated
Comment thread cli/plugin-kit-ai/internal/agentpluginscli/read.go
Comment thread npm/agentplugins/scripts/platform-proof.js Outdated
Comment thread npm/agentplugins/scripts/platform-proof.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
npm/agentplugins/test/platform-proof.test.js (1)

74-81: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the on-disk byte mismatch branch of frozenReleaseAsset.

Both rejection cases assert /manifest does not match/. Both fail in the first branch of frozenReleaseAsset, because the manifest comparison runs before the file is read. The second branch, "frozen release binary does not match the npm package size and SHA-256 pin", is never exercised. That branch is the byte-level check that binds the local file to the release pin.

Add a case where the manifest and the pin agree but the file on disk holds different bytes.

♻️ Proposed additional case
   assert.throws(
     () => frozenReleaseAsset(root, commit, version, target, { ...pinned, sha256: "0".repeat(64) }),
     /manifest does not match/
   );
+  fs.writeFileSync(path.join(root, file), Buffer.from("tampered frozen binary"));
+  assert.throws(
+    () => frozenReleaseAsset(root, commit, version, target, pinned),
+    /binary does not match/
+  );
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@npm/agentplugins/test/platform-proof.test.js` around lines 74 - 81, Add a
test case around the existing frozenReleaseAsset assertions where the manifest
and pinned metadata remain consistent, but the on-disk asset is replaced or
written with different bytes; assert the byte-level mismatch error for the
frozen release binary, thereby exercising the second branch after manifest
validation succeeds.
.github/workflows/agentplugins-platform-proof.yml (1)

294-309: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Move the expression expansions into env: to satisfy zizmor.

zizmor reports template injection on lines 295, 297, 302, 303, 307, and 308. The expanded values are not attacker-controlled today. prepare validates inputs.expected_commit against ^[0-9a-f]{40}$ at line 144 and gates this job through needs: prepare, and the needs.prepare.outputs.* values come from the validated tag and from npm pack --json.

Binding the values through env: removes the finding and keeps the step safe if the upstream validation is ever relaxed.

♻️ Proposed change
       - name: Run native npm launcher and released binary proof
         shell: bash
+        env:
+          EXPECTED_COMMIT: ${{ inputs.expected_commit }}
+          BOOTSTRAP_MODE: ${{ needs.prepare.outputs.bootstrap_mode }}
+          RELEASE_VERSION: ${{ needs.prepare.outputs.version }}
+          TARBALL_FILE: ${{ needs.prepare.outputs.tarball_file }}
+          PROOF_TARGET: ${{ matrix.target }}
+          PROOF_LIFECYCLE: ${{ matrix.lifecycle }}
         run: |
-          test "$(git rev-parse HEAD)" = "${{ inputs.expected_commit }}"
+          test "$(git rev-parse HEAD)" = "${EXPECTED_COMMIT}"
           release_assets="-"
-          if [[ "${{ needs.prepare.outputs.bootstrap_mode }}" = "local_frozen_asset" ]]; then
+          if [[ "${BOOTSTRAP_MODE}" = "local_frozen_asset" ]]; then
             release_assets="${{ runner.temp }}/agentplugins-proof-package/release-assets"
             test -d "${release_assets}"
           fi
           node npm/agentplugins/scripts/platform-proof.js \
-            "${{ runner.temp }}/agentplugins-proof-package/${{ needs.prepare.outputs.tarball_file }}" \
-            "${{ needs.prepare.outputs.version }}" \
-            "${{ matrix.target }}" \
-            "${{ matrix.lifecycle }}" \
-            "${{ runner.temp }}/agentplugins-proof-${{ matrix.target }}.json" \
-            "${{ inputs.expected_commit }}" \
-            "${{ needs.prepare.outputs.bootstrap_mode }}" \
+            "${{ runner.temp }}/agentplugins-proof-package/${TARBALL_FILE}" \
+            "${RELEASE_VERSION}" \
+            "${PROOF_TARGET}" \
+            "${PROOF_LIFECYCLE}" \
+            "${{ runner.temp }}/agentplugins-proof-${PROOF_TARGET}.json" \
+            "${EXPECTED_COMMIT}" \
+            "${BOOTSTRAP_MODE}" \
             "${release_assets}"

Note: the Upload machine-readable native proof step at line 315 must keep the same result path, so matrix.target stays in that path: expression.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/agentplugins-platform-proof.yml around lines 294 - 309,
Move the interpolated inputs used by the proof step’s shell script into the
step-level env configuration, then reference those environment variables in the
git check, bootstrap-mode branch, release-assets path, and platform-proof.js
arguments. Preserve the existing values and behavior, while leaving
matrix.target in the Upload machine-readable native proof step’s path expression
unchanged.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@install/integrationctl/agentplugins/planner/planner.go`:
- Around line 159-165: Update verify() to explicitly validate
CatalogCompatibility.Verification against the supported values, including
schema_only and not_tested; reject or map any unknown non-empty value to the
appropriate unverified/unsupported state before compatibility is accepted, while
preserving the existing warning and user-action behavior for supported values.
- Around line 90-97: Update the planner flow around the authentication checks
and plan status transitions so AuthenticationPending and
AuthenticationNotChecked produce PlanManualActivationRequired with
ActivationManual, rather than PlanReady or ActivationPrepared. Permit staging
and lifecycle completion only when authentication is AuthenticationNotRequired
or AuthenticationComplete; preserve the existing unsupported-plan handling and
user-action messages.

In `@npm/agentplugins/scripts/platform-proof.js`:
- Around line 146-148: Add AGENTPLUGINS_INTERNAL_PROOF_MODE and
AGENTPLUGINS_INTERNAL_PROOF_BINARY to the environment-variable removal list in
npm/agentplugins/scripts/platform-proof.js so the public_release_download branch
cannot inherit local overrides; preserve the local_frozen_asset branch’s later
assignment of these variables. No change is needed in
docs/agentplugins-release.md lines 94-95 because the existing wording remains
correct after this fix.

---

Nitpick comments:
In @.github/workflows/agentplugins-platform-proof.yml:
- Around line 294-309: Move the interpolated inputs used by the proof step’s
shell script into the step-level env configuration, then reference those
environment variables in the git check, bootstrap-mode branch, release-assets
path, and platform-proof.js arguments. Preserve the existing values and
behavior, while leaving matrix.target in the Upload machine-readable native
proof step’s path expression unchanged.

In `@npm/agentplugins/test/platform-proof.test.js`:
- Around line 74-81: Add a test case around the existing frozenReleaseAsset
assertions where the manifest and pinned metadata remain consistent, but the
on-disk asset is replaced or written with different bytes; assert the byte-level
mismatch error for the frozen release binary, thereby exercising the second
branch after manifest validation succeeds.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 222fb9d7-51e6-4cbc-a10a-da3fb8877204

📥 Commits

Reviewing files that changed from the base of the PR and between 24d9f78 and cf42065.

📒 Files selected for processing (20)
  • .github/workflows/agentplugins-npm-publish.yml
  • .github/workflows/agentplugins-platform-proof.yml
  • .github/workflows/agentplugins-release.yml
  • cli/plugin-kit-ai/internal/agentpluginscli/add.go
  • cli/plugin-kit-ai/internal/agentpluginscli/binding.go
  • cli/plugin-kit-ai/internal/agentpluginscli/cli_test.go
  • cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go
  • cli/plugin-kit-ai/internal/agentpluginscli/read.go
  • cli/plugin-kit-ai/internal/agentpluginscli/state_migration.go
  • docs/agentplugins-release.md
  • install/integrationctl/agentplugins/adapters/clientdetect/detector.go
  • install/integrationctl/agentplugins/adapters/clientdetect/detector_test.go
  • install/integrationctl/agentplugins/planner/planner.go
  • install/integrationctl/agentplugins/planner/planner_test.go
  • npm/agentplugins/bin/agentplugins.js
  • npm/agentplugins/lib/bootstrap.js
  • npm/agentplugins/scripts/platform-proof.js
  • npm/agentplugins/test/bootstrap.test.js
  • npm/agentplugins/test/platform-proof.test.js
  • repotests/agentplugins_release_contract_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • install/integrationctl/agentplugins/planner/planner_test.go
  • install/integrationctl/agentplugins/adapters/clientdetect/detector_test.go
  • .github/workflows/agentplugins-release.yml
  • install/integrationctl/agentplugins/adapters/clientdetect/detector.go
  • cli/plugin-kit-ai/internal/agentpluginscli/add.go
  • cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go
  • .github/workflows/agentplugins-npm-publish.yml

Comment on lines +90 to +97
if plan.Status == domain.PlanUnsupported {
return plan, nil
}
if plan.Authentication == domain.AuthenticationPending {
plan.UserActions = append(plan.UserActions, "complete authentication for this plugin in the selected client")
} else if plan.Authentication == domain.AuthenticationNotChecked {
plan.UserActions = append(plan.UserActions, "verify the plugin's authentication requirements before using it")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expect: all activation and completion paths gate on authentication state.
rg -n -C 8 \
  '\b(AuthenticationPending|AuthenticationNotChecked|PlanReady|ActivationPrepared|DeliveryPlan)\b' \
  --glob '*.go' .

Repository: 777genius/plugin-kit-ai

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find delivery/lifecycle consumer files =="
git ls-files '*.go' | rg '(activator|transaction|kernel|stager|lifecycle|operation|install|complete|apply|activate|stage)' || true

echo
echo "== Search for ActivationPrepared and PlanReady gate conditions =="
python3 - <<'PY'
import subprocess, pathlib, re
files = subprocess.check_output(['git','ls-files','*.go'], text=True).splitlines()
for f in files:
    txt = pathlib.Path(f).read_text()
    hits = re.findall(r'\b(ActivationPrepared|PlanReady)\b', txt)
    if hits:
        line_map = {}
        for m in re.finditer(r'\b(ActivationPrepared|PlanReady)\b', txt):
            line = txt[:m.start()].count('\n') + 1
            line_map.setdefault(line, []).append(m.group(1))
        if len(line_map) <= 40:
            print(f"\n--- {f} ---")
            for line in sorted(line_map):
                start=max(1,line-4); end=line+4
                print(f"lines {start}-{end}:")
                for i,s in enumerate(txt.splitlines()[start-1:end], start):
                    print(f"{i:4}: {s}")
PY

echo
echo "== Search exact consumer names around lifecycle/complete/stage/activate =="
rg -n -C 5 'Authentication(Pending|NotChecked|NotRequired|Complete|Failed)|Plan(Status|Ready|ManualActivationRequired|Unsupported)|Activation(Prepared|Active|Manual|Failed|NotRequired)|Complete|Lifecycle|Stage\(|Activate\(' \
  install/integrationctl/agentplugins --glob '*.go' | head -n 300

Repository: 777genius/plugin-kit-ai

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Planner relevant section =="
sed -n '70,105p' install/integrationctl/agentplugins/planner/planner.go

echo
echo "== Usecase service staging/bind path relevant section =="
sed -n '540,590p' install/integrationctl/agentplugins/usecase/service.go

echo
echo "== Provider Stager relevant section =="
sed -n '90,180p' install/integrationctl/agentplugins/providers/stager.go

echo
echo "== All functions calling DeliveryPlanner Plan / PackageStager Stage / kernel.ApplyDirectory =="
rg -n -C 4 '(\.Plan\(|\.Stage\(|ApplyDirectory\(|Activation:\s*state\.C|Activation:\s*plan\.Authentication|Authentication:\s*plan\.Authentication)' \
  install/integrationctl/agentplugins --glob '*.go' | head -n 320

Repository: 777genius/plugin-kit-ai

Length of output: 40523


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Activation readiness/use paths =="
rg -n -C 5 'initialLifecycle|ActivationPrepared|ActivationManual|ActivationActive|Authenticate|Authentication' \
  install/integrationctl/agentplugins --glob '*.go' | head -n 260

echo
echo "== service.go around add flow =="
sed -n '100,280p' install/integrationctl/agentplugins/usecase/service.go

echo
echo "== service.go around initialLifecycle =="
rg -n -C 8 'func initialLifecycle' install/integrationctl/agentplugins --glob '*.go'

Repository: 777genius/plugin-kit-ai

Length of output: 36280


Gate prepared activation behind satisfied authentication states.

PlanReady/ActivationPrepared are produced, staged, and committed even when plan.Authentication is AuthenticationPending or AuthenticationNotChecked. Change the planner to downgrade these cases to PlanManualActivationRequired/ActivationManual, and reject staging/lifecycle completion until authentication becomes AuthenticationNotRequired or AuthenticationComplete.

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 93-93: QF1003: could use tagged switch on plan.Authentication

(staticcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/planner/planner.go` around lines 90 - 97,
Update the planner flow around the authentication checks and plan status
transitions so AuthenticationPending and AuthenticationNotChecked produce
PlanManualActivationRequired with ActivationManual, rather than PlanReady or
ActivationPrepared. Permit staging and lifecycle completion only when
authentication is AuthenticationNotRequired or AuthenticationComplete; preserve
the existing unsupported-plan handling and user-action messages.

Comment on lines +159 to +165
if compatibility.Verification == "schema_only" || compatibility.Verification == "not_tested" {
plan.Warnings = appendUnique(plan.Warnings, "catalog_"+compatibility.Verification)
if plan.Status != domain.PlanUnsupported {
plan.UserActions = append(plan.UserActions, "verify the plugin in the selected client before relying on it")
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expect: the catalog schema or loader rejects unknown verification values.
rg -n -C 8 \
  'CatalogCompatibility|Verification|schema_only|not_tested|tested' \
  --glob '*.go' --glob '*.json' --glob '*.yaml' --glob '*.yml' .

Repository: 777genius/plugin-kit-ai

Length of output: 50380


Reject unknown catalog verification values before accepting compatibility.

verify() only flags schema_only and not_tested; other non-empty values fall through without a warning or status change. Add explicit validation for CatalogCompatibility.Verification against the supported allowlist, or map unknown values to an unverified/unsupported state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install/integrationctl/agentplugins/planner/planner.go` around lines 159 -
165, Update verify() to explicitly validate CatalogCompatibility.Verification
against the supported values, including schema_only and not_tested; reject or
map any unknown non-empty value to the appropriate unverified/unsupported state
before compatibility is accepted, while preserving the existing warning and
user-action behavior for supported values.

Comment on lines +146 to +148
for (const name of ["CODEX_HOME", "CLAUDE_CONFIG_DIR", "CURSOR_CONFIG_DIR", "NPM_TOKEN", "NODE_AUTH_TOKEN", "GITHUB_TOKEN", "GH_TOKEN"]) {
delete env[name];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The public-release proof does not enforce the anonymous-download guarantee it documents. The runbook states that the public-release proof uses a cold cache without the local override, so it proves the normal anonymous GitHub release download. The proof script does not remove the internal proof variables from the inherited environment, so public_release_download mode can silently bootstrap from a local asset and still record anonymous_public_release_download: true.

  • npm/agentplugins/scripts/platform-proof.js#L146-L148: add AGENTPLUGINS_INTERNAL_PROOF_MODE and AGENTPLUGINS_INTERNAL_PROOF_BINARY to the removal list, so the public_release_download branch cannot inherit a local override from the runner. The local_frozen_asset branch at line 172 sets them again after sanitation.
  • docs/agentplugins-release.md#L94-L95: keep the wording once the script enforces the guarantee; no documentation change is needed after the fix.
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 148-148: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFileSync(env.NPM_CONFIG_USERCONFIG, "registry=https://registry.npmjs.org/\n")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename)

📍 Affects 2 files
  • npm/agentplugins/scripts/platform-proof.js#L146-L148 (this comment)
  • docs/agentplugins-release.md#L94-L95
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@npm/agentplugins/scripts/platform-proof.js` around lines 146 - 148, Add
AGENTPLUGINS_INTERNAL_PROOF_MODE and AGENTPLUGINS_INTERNAL_PROOF_BINARY to the
environment-variable removal list in npm/agentplugins/scripts/platform-proof.js
so the public_release_download branch cannot inherit local overrides; preserve
the local_frozen_asset branch’s later assignment of these variables. No change
is needed in docs/agentplugins-release.md lines 94-95 because the existing
wording remains correct after this fix.

@777genius
777genius merged commit 38656ef into main Aug 9, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant