Skip to content

feat: Teams asset sharing dashboard, remove Claude Code installation, docs and pricing updates#90

Merged
maxritter merged 7 commits intomainfrom
dev
Mar 5, 2026
Merged

feat: Teams asset sharing dashboard, remove Claude Code installation, docs and pricing updates#90
maxritter merged 7 commits intomainfrom
dev

Conversation

@maxritter
Copy link
Owner

@maxritter maxritter commented Mar 2, 2026

Summary

Redesigns the Teams feature in the Console dashboard for team asset sharing (rules, skills, commands, agents) and removes Claude Code installation from Pilot Shell — it's now a prerequisite users install themselves.

Changes

Teams Asset Sharing Dashboard

  • Redesigned Teams UI with guided onboarding flow for new team members
  • Project-scoped asset management — assets tied to repositories
  • Flat asset table with content viewing modal
  • Push local assets and install shared assets from the Console UI
  • Help modal with asset type reference

Claude Code Installation Removed

  • Deleted launcher/claude_installer.py — Pilot Shell no longer installs or updates Claude Code
  • Removed _update_claude_code() from the launcher startup flow
  • Removed install_claude_code() from the installer dependencies step
  • Added Claude Code as a prerequisite in README and docs site
  • Cleaned up all related tests

Documentation & Pricing

  • Updated docs site prerequisites section with Claude Code requirement
  • Updated installer step descriptions (README + docs site)
  • Consistent sentence case and em-dash style across pricing section
  • Fixed "Continous" typo in pricing
  • Removed legacy tier names, updated license table

Other

  • Improved spec stop guard and plan question handling
  • Removed legacy Vault/Vexor terminology from docs

Commits

  • feat: overhaul Teams dashboard with flat UI, action buttons, and vault cleanup
  • fix: redesign Teams feature with onboarding, scoped naming, and improved asset management
  • fix: apply stashed changes — remove legacy tier names, improve spec stop guard and plan questions
  • fix: remove Claude Code installation, add as prerequisite, update Teams and pricing consistency

Testing

  • 882 launcher tests passed
  • 314 installer tests passed
  • 1070 console tests passed
  • Console typecheck clean

Summary by CodeRabbit

  • New Features

    • Teams asset management UI: discover, push, install, update, remove, sync, and per-asset actions; Teams repo setup and guided onboarding; Team-gated upgrade prompts and push panel.
  • Refactor

    • Replaced Vault with Teams across UI, navigation, shortcuts, hooks, and dashboard components; branding and workflow updated to Teams/Console.
  • Documentation

    • New Teams "Team Asset Sharing" docs, updated README, pricing and FAQ text.
  • Chores

    • Installer/prerequisites revised: Claude Code noted; installer now targets language servers and updated post-install steps.

…t cleanup

- Remove 4-tab layout (Assets/Push/Setup/Roles), flatten to single page
- Add per-asset action buttons: Install, Update (via sx remove+add), Remove
- Add new POST /api/teams/update-asset endpoint for proper version updates
- Inline push panel with "Push to:" scope selector
- Add Reconfigure button to repository setup section
- Remove Roles feature entirely (TeamsRolesTab, roles endpoints, hook methods)
- Rename all Vault* types to Teams* (VaultStatus→TeamsStatusResponse, etc.)
- Rename vaultUrl→repoUrl across full stack (JSON wire format + consumers)
- Clean up vault references in settings.json, installer, rules, README
- Update navigation: /vault→/teams route, sidebar, command palette, shortcuts
@vercel
Copy link

vercel bot commented Mar 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
pilot-shell Ignored Ignored Preview Mar 5, 2026 1:43pm

Request Review

@claude
Copy link

claude bot commented Mar 2, 2026

Claude finished @maxritter's task in 2m 9s —— View job


Code Review

  • Gather context and read CLAUDE.md
  • Review TeamsRoutes.ts (new backend endpoint)
  • Review Teams UI components
  • Review useTeams hook changes
  • Review test coverage
  • Compile findings and post review

Overall this is a solid, well-structured overhaul. The flat dashboard design is cleaner, the Vault→Teams rename is thorough, and the new update-asset endpoint's remove+add strategy correctly solves the pinned-version limitation. Test coverage is good. A few issues worth addressing below.


Bugs

1. Version comparison only uses major version (useTeams.ts:97-101)

parseVersion strips the v prefix then calls parseInt(..., 10), which only parses the integer before the first non-digit. Version "1.9.5" and "1.2.0" both parse to 1, so minor/patch updates are never detected as hasUpdate. Assets on v1.0.0 with a v1.9.5 available will never show the Update badge.

// Current:
return parseInt(stripped, 10);  // "1.9.5" → 1

// Fix: compare full semver or at least major.minor.patch as a tuple

Fix this →


2. Install button routes through updateAsset with broken scope logic (TeamsAssetTable.tsx:213-221, Teams/index.tsx:68-69)

The "Install" button for uninstalled assets calls handleActionClickonUpdate(asset). handleUpdate then does:

asset.scope === "Global" ? "global" : "project",   // uninstalled → scope is null → "project"
asset.scope !== "Global" ? asset.scope : null,       // uninstalled → scope is null → null

With scope="project" and scopeUrl=null, the backend's handleUpdateAsset hits the else-branch:

} else {
  addArgs.push("--scope-global");   // ← always falls through to global!
}

So every "Install" click adds the asset as global scope regardless of what was intended. The same else-branch fallback in handleUpdateAsset also affects project-scoped assets when no scopeUrl is available.

Fix this →


3. No atomicity in handleUpdateAsset — remove succeeds, add fails, asset is gone (TeamsRoutes.ts:470-511)

The update sequence is: sx removesx addsx install --repair. If sx remove succeeds but sx add fails (network error, auth issue, etc.), the asset is permanently deleted from the lock file with no rollback. The user then has no installed version and the update has silently failed.

Mitigation options: store the old version before removing, and re-add it on failure; or at minimum return a more actionable error message indicating the asset was removed but reinstallation failed.


4. runSxCommand leaks the inner SIGKILL timer (TeamsRoutes.ts:543-569)

The outer SIGTERM setTimeout is cleared in finally, but the nested SIGKILL setTimeout (1000ms after SIGTERM) is not cancelled if the process exits normally before it fires. This leaves a dangling timer that will attempt to kill an already-dead process handle.

const killId = setTimeout(() => {
  try { proc.kill("SIGKILL"); } catch {}
}, 1000);
// clearTimeout(killId) is never called

Minor Issues

5. TeamsPushPanel has no error boundary on the push loop (TeamsPushPanel.tsx:70-95)

If pushAsset throws (network error), isPushing stays true and the button is permanently disabled until page reload. Wrapping the loop in try/finally would fix this.

Fix this →


6. sx vault list CLI call inside Teams context (TeamsRoutes.ts:110)

this.runSxCommand([sxPath, "vault", "list", "--json"], ...)

The PR renames everything Vault*Teams* in the TypeScript layer, but the underlying CLI commands still use vault (which is correct — the CLI hasn't changed). This is fine, but worth a comment to avoid future confusion about why vault appears in TeamsRoutes.


7. updateAsset missing from test coverage (teams-routes.test.ts)

The new handleUpdateAsset endpoint (the most complex one in this PR) has no tests. Given the remove-then-add semantics and the partial-failure risk noted in #3, this endpoint is the one most in need of unit tests covering: missing currentVersion (skip remove), failed remove, failed add-after-remove, successful round-trip.


8. toggleAll in TeamsPushPanel uses exact length equality (TeamsPushPanel.tsx:63-67)

if (prev.size === assets.length) return new Set();

This only deselects-all when all are selected. Selecting 3 of 4, then clicking toggle-all, selects the 4th rather than deselecting all — the standard UX expectation is that a partially-checked header checkbox deselects all. Minor UX nit.


Positive Notes

  • The path-traversal guard in handlePush is correct and well-placed.
  • Input validation with NAME_REGEX applied consistently across all asset name parameters.
  • Caching with TTL on both status and detail calls is well-implemented.
  • The confirm-before-remove pattern (3s timeout) in AssetRow is a nice touch.
  • TeamGate cleanly separates tier-gating from the business logic.
  • Good test coverage overall for the happy paths and most error cases.

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 234b1f5d-823c-4857-b44f-09946cb26947

📥 Commits

Reviewing files that changed from the base of the PR and between 0a2e3f7 and 3303648.

⛔ Files ignored due to path filters (4)
  • pilot/rules/team-sharing.md is excluded by !pilot/**
  • pilot/scripts/worker-service.cjs is excluded by !pilot/**
  • pilot/ui/viewer-bundle.js is excluded by !pilot/**
  • pilot/ui/viewer.css is excluded by !pilot/**
📒 Files selected for processing (6)
  • README.md
  • console/src/services/worker/http/routes/TeamsRoutes.ts
  • console/src/ui/viewer/components/TeamGate.tsx
  • console/src/ui/viewer/views/Teams/index.tsx
  • console/tests/ui/teams-view.test.ts
  • docs/site/src/pages/docs/RulesSection.tsx

Walkthrough

Migrates "Vault" to "Teams" across backend, frontend, docs, tests, and installer. Adds a new TeamsRoutes API and Teams UI (views, setup, push, gating), removes Vault routes and UI, updates hooks and types to Teams, adjusts installer/dependency handling (removes automated Claude Code install), and updates CI, docs, and tests accordingly.

Changes

Cohort / File(s) Summary
Backend routing & worker
console/src/services/worker-service.ts, console/src/services/worker/http/routes/TeamsRoutes.ts, console/src/services/worker/http/routes/VaultRoutes.ts
Removes VaultRoutes; registers new TeamsRoutes. Adds many /api/teams/... endpoints, in-memory caching/TTLs, input/path validation, sx CLI execution with timeouts and serialized install handling; Vault CLI/route logic deleted.
Frontend views & routing
console/src/ui/viewer/App.tsx, console/src/ui/viewer/views/index.ts, console/src/ui/viewer/views/Vault/... (removed), console/src/ui/viewer/views/Teams/...
Replaces VaultView with TeamsView in routes and exports. Removes Vault view files. Adds TeamsView and a full set of Teams-specific view components (status, setup, push panel, asset table, detail, summary cards).
Hooks & state
console/src/ui/viewer/hooks/useTeams.ts, console/src/ui/viewer/hooks/useStats.ts, console/src/ui/viewer/hooks/useSettings.ts
Renames/refactors useVault → useTeams and Vault* types → Teams*; switches API calls to /api/teams/...; removes default vault command from DEFAULT_SETTINGS; adds new Teams methods (discover, pushAsset, initTeams, removeAsset, updateAsset) and updates returned public shapes.
UI components & navigation
console/src/ui/viewer/components/TeamGate.tsx, console/src/ui/viewer/components/CommandPalette.tsx, console/src/ui/viewer/layouts/Sidebar/SidebarNav.tsx, console/src/ui/viewer/constants/shortcuts.ts, console/src/ui/viewer/components/CommandPalette.tsx
Adds TeamGate component for plan gating; changes nav item and command palette entries from Vault → Teams; updates keyboard shortcuts and sequences to target /teams; normalizes string quoting.
Teams feature UI behaviors
console/src/ui/viewer/views/Teams/TeamsPushPanel.tsx, console/src/ui/viewer/views/Teams/TeamsSetupTab.tsx, console/src/ui/viewer/views/Teams/index.tsx, console/src/ui/viewer/views/Teams/TeamsAssetTable.tsx, console/src/ui/viewer/views/Teams/TeamsAssetDetail.tsx
Implements discover/push flow, repo init/reconfigure UI, push sequencing with per-asset results, per-asset actions (install/update/remove), detail fetching, toasts, and gating wiring.
Tests added/updated
console/tests/worker/teams-routes.test.ts, console/tests/hooks/use-teams.test.ts, console/tests/ui/teams-*.test.ts, console/tests/* (vault tests removed)
Adds Teams-focused unit/UI/route tests; updates many tests to reference useTeams and /api/teams/...; removes Vault-specific test suites.
Docs & website
README.md, docs/site/src/pages/docs/TeamsSection.tsx, docs/site/src/components/TeamsDashboardSection.tsx, various docs/pages/components/...`
Rewords docs to use Teams terminology; adds TeamsDashboardSection and other Teams docs content; replaces Vault references across site, blog posts, and pages.
Installer & dependencies
installer/steps/dependencies.py, installer/steps/finalize.py, installer/cli.py, installer/ui.py, installer/tests/unit/steps/test_dependencies.py
Removes automated Claude Code install logic and related tests; updates dependency list to language servers; disables specific sx clients post-install; removes /vault post-install step; narrows license tier mapping.
CI & repo config
.gitattributes, .github/workflows/*.yml (claude.yml, release*.yml), .coderabbit.yaml, .gitignore, .trivyignore
Adds git-crypt install/unlock steps in workflows; excludes console/** from certain review flows and scanners; adjusts git-crypt attributes and ignore patterns; adds new ignore entries.
Docs site additions
docs/site/src/components/TeamsDashboardSection.tsx, docs/site/src/pages/Index.tsx
Adds TeamsDashboardSection component and surfaces it on the site index and docs pages.
Misc & legal
LICENSE, .github/ISSUE_TEMPLATE/*
Small LICENSE footer edits; adds a feature request issue template and removes a contact_links block.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant TeamsView as TeamsView (UI)
  participant UseTeams as useTeams Hook
  participant Worker as Worker (HTTP /api/teams)
  participant SX as sx CLI
  participant GitFS as Git / Filesystem

  Browser->>TeamsView: user action (open Teams / push / install)
  TeamsView->>UseTeams: call discover / push / init / update / remove
  UseTeams->>Worker: HTTP POST /api/teams/... (discover|push|init|install|remove)
  Worker->>SX: spawn `sx ...` command (with timeout)
  SX->>GitFS: read/write repo or .claude dir (push/discover)
  SX-->>Worker: command stdout/exit (JSON or text)
  Worker-->>UseTeams: JSON response (status, detail, errors)
  UseTeams-->>TeamsView: update UI state (toasts, table, summaries)
  TeamsView-->>Browser: render updated asset states
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: introducing a Teams asset sharing dashboard, removing Claude Code installation, and updating documentation and pricing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (7)
README.md-315-315 (1)

315-315: ⚠️ Potential issue | 🟡 Minor

Complete the README terminology migration to avoid user confusion.

Good update here, but the README still references legacy Vault terms elsewhere (for example: Line 158 /vault, Line 186 Vault view, Line 517 /vault). If /teams is now the canonical route/feature name, these should be updated in the same pass so docs match product behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 315, The README still uses legacy "Vault" terminology and
routes (e.g., occurrences of "/vault", "Vault" view, and the legacy file
reference `team-vault.md`) which should be migrated to the new canonical "teams"
naming; search for and replace all references of "/vault" with "/teams", rename
mentions of "Vault" view to "Teams" view (preserving context/capitalization),
and update or remove `team-vault.md` references to the new `teams` documentation
or filename so the docs consistently reflect the product naming.
console/src/ui/viewer/components/CommandPalette.tsx-124-132 (1)

124-132: ⚠️ Potential issue | 🟡 Minor

Arrow-key navigation breaks when no commands are available.

When filteredCommands.length === 0, modulo operations set selectedIndex to NaN.

🔧 Suggested guard
   const handleKeyDown = (e: React.KeyboardEvent) => {
+    if (
+      filteredCommands.length === 0 &&
+      (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter")
+    ) {
+      e.preventDefault();
+      return;
+    }
+
     switch (e.key) {
       case "ArrowDown":
         e.preventDefault();
         setSelectedIndex((i) => (i + 1) % filteredCommands.length);
         break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/components/CommandPalette.tsx` around lines 124 - 132,
Arrow-key handlers in CommandPalette.tsx compute new selected index using modulo
which yields NaN when filteredCommands.length === 0; add a guard in the
ArrowDown and ArrowUp cases (or before invoking setSelectedIndex) to no-op when
filteredCommands.length === 0 so setSelectedIndex is not called with invalid
math, keeping the current selection unchanged (or explicitly set to a safe
default like -1/0 if that state is handled elsewhere).
console/src/ui/viewer/views/Teams/TeamsSummaryCards.tsx-49-54 (1)

49-54: ⚠️ Potential issue | 🟡 Minor

Keep the stat description aligned with the displayed metric.

When title/value include “Other”, the description “Slash commands” is misleading.

💡 Suggested tweak
-          <div className="stat-desc">Slash commands</div>
+          <div className="stat-desc">
+            {other > 0 ? "Slash commands + other asset types" : "Slash commands"}
+          </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Teams/TeamsSummaryCards.tsx` around lines 49 -
54, The stat description in TeamsSummaryCards.tsx (the elements with classNames
stat-title, stat-value, stat-desc that display commands and other via variables
commands and other) can become misleading when other > 0; update the rendering
logic for the stat-desc so it conditionally matches the displayed metric (e.g.,
when other > 0 show a description like "Slash commands and other" or similar,
otherwise keep "Slash commands") so the description aligns with the title/value.
console/tests/ui/teams-view.test.ts-116-116 (1)

116-116: ⚠️ Potential issue | 🟡 Minor

Update stale test naming to Teams terminology.

The test title still says “vault assets”; rename it to “team assets” for accurate behavior description.

As per coding guidelines, "Review test code briefly. Focus on: Clear test names that describe behavior".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/ui/teams-view.test.ts` at line 116, Rename the test title
string in teams-view.test.ts from "renders install button for uninstalled vault
assets" to "renders install button for uninstalled team assets" so the test name
matches the Teams terminology; locate the it(...) call in the file (the test
case starting with it("renders install button for uninstalled vault assets",
...) and update only the descriptive string, leaving the test body and
assertions unchanged.
console/src/ui/viewer/views/Teams/index.tsx-41-42 (1)

41-42: ⚠️ Potential issue | 🟡 Minor

Avoid gating with null tier before license load completes.

Because useLicense().isLoading isn’t used, TeamGate receives null during fetch and can briefly render the locked/upgrade state for eligible users. Gate these sections only after license loading settles.

Also applies to: 182-190, 210-220, 254-256

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Teams/index.tsx` around lines 41 - 42, The Team
gating logic is using license which can be null while the license is still
loading; update the code that calls useLicense() to also read isLoading and
defer rendering any TeamGate checks until loading settles (for example, return a
loader or skip rendering TeamGate when isLoading is true), and then pass the
resolved license/tier (or undefined) into TeamGate instead of null so eligible
users don't briefly see locked UI; apply the same change to all TeamGate usages
referenced (the blocks around where useLicense() is read and TeamGate is
rendered).
console/src/ui/viewer/views/Dashboard/TeamsStatus.tsx-68-71 (1)

68-71: ⚠️ Potential issue | 🟡 Minor

Use a composite key for availability counts (name-only matching is lossy).

Current logic matches installed/catalog entries by name only. If two assets share a name across types, availableCount is incorrect (Line [68]-Line [71]). Match on type + name to avoid false exclusions.

Proposed fix
-  const installedNames = new Set(assets.map((a) => a.name));
+  const installedKeys = new Set(assets.map((a) => `${a.type}:${a.name}`));
   const availableCount = catalog.filter(
-    (c) => !installedNames.has(c.name),
+    (c) => !installedKeys.has(`${c.type}:${c.name}`),
   ).length;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Dashboard/TeamsStatus.tsx` around lines 68 - 71,
The availability check in TeamsStatus.tsx builds installedNames from assets
using name-only and computes availableCount by filtering catalog by name, which
breaks when assets share names across types; update the logic to use a composite
key (e.g., `${type}::${name}`) for both installedNames and the catalog filter so
matching is done on type+name (ensure you reference the installedNames set and
availableCount calculation), and handle possible missing/undefined type values
consistently when building the composite keys.
console/src/ui/viewer/views/Teams/TeamsSetupTab.tsx-66-71 (1)

66-71: ⚠️ Potential issue | 🟡 Minor

Replace stale “vault” placeholders in Teams UI copy.

Lines [68]-[70] still show legacy vault wording (team-vault.git, /path/to/vault). This is user-facing and inconsistent with the Teams rename.

Proposed fix
           placeholder={
             repoType === "git"
-              ? "git@github.com:org/team-vault.git"
+              ? "git@github.com:org/team-repo.git"
               : repoType === "path"
-                ? "/path/to/vault"
+                ? "/path/to/team-repo"
                 : "https://skills.new/..."
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Teams/TeamsSetupTab.tsx` around lines 66 - 71, In
TeamsSetupTab (the placeholder ternary using repoType), replace legacy "vault"
wording in the user-facing placeholders: change
"git@github.com:org/team-vault.git" to a non-vault name (e.g.,
"git@github.com:org/team-repo.git") and change "/path/to/vault" to a matching
local path (e.g., "/path/to/team-repo" or "/path/to/teams") so the placeholders
reflect the Teams rename and remain consistent with the UI copy.
🧹 Nitpick comments (4)
console/tests/hooks/useSettings.test.ts (1)

47-53: Strengthen this test to assert the exact command key set.

Line 47 says “all six commands,” but the current assertions allow additional unexpected keys (except vault). Assert exact keys to catch accidental defaults drift.

Proposed test tightening
   it('DEFAULT_SETTINGS has all six commands', async () => {
     const { DEFAULT_SETTINGS } = await import('../../src/ui/viewer/hooks/useSettings.js');
     const expected = ['spec', 'spec-plan', 'spec-implement', 'spec-verify', 'sync', 'learn'];
-    for (const cmd of expected) {
-      expect(DEFAULT_SETTINGS.commands[cmd]).toBeDefined();
-    }
-    expect(DEFAULT_SETTINGS.commands['vault']).toBeUndefined();
+    expect(Object.keys(DEFAULT_SETTINGS.commands).sort()).toEqual(expected.sort());
   });

As per coding guidelines, "**/tests/**: Review test code briefly. Focus on: Test coverage for the feature being tested."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/hooks/useSettings.test.ts` around lines 47 - 53, The test
currently checks only presence of six expected commands and absence of 'vault',
so update the assertion to verify the exact set of keys in
DEFAULT_SETTINGS.commands to prevent accidental additions; in the test for
DEFAULT_SETTINGS (in useSettings.test.ts) import DEFAULT_SETTINGS and replace
the per-key checks with a single assertion that
Object.keys(DEFAULT_SETTINGS.commands) (sorted) strictly equals the expected
array (sorted) — referencing DEFAULT_SETTINGS and its commands property to
locate the code to change.
console/tests/worker/teams-routes.test.ts (1)

168-190: Discover test should avoid real fs/git dependencies and sleep-based waiting.

Mock filesystem/git interactions and await the handler directly to keep tests deterministic and non-flaky.

✅ Minimal deterministic pattern
-      discoverHandler({}, fakeRes);
-      await new Promise((r) => setTimeout(r, 50));
+      await discoverHandler({}, fakeRes);
As per coding guidelines, "`**/tests/**`: Review test code briefly. Focus on: Proper mocking of external dependencies".

Also applies to: 184-186

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/worker/teams-routes.test.ts` around lines 168 - 190, The test
calls TeamsRoutes.setupRoutes then invokes the discover handler and uses a sleep
to wait; instead, mock external FS/Git interactions used by TeamsRoutes (e.g.,
any fs, git, or repo helpers it depends on) using jest mocks/spies and call the
route handler as an async function so you can await it directly; locate the
route via TeamsRoutes.setupRoutes and the discoverHandler reference, provide a
fakeReq/fakeRes that returns a Promise from json/status (or make the handler
return a Promise) and then await discoverHandler(fakeReq, fakeRes) instead of
using setTimeout to make the test deterministic and avoid touching the real
filesystem or git.
console/tests/ui/teams-navigation.test.ts (1)

11-49: Prefer module/behavior assertions over file-content matching for routing wiring.

These checks are mostly string scans, which are brittle and can miss real wiring regressions. Prefer asserting exported route/nav/command data structures directly where possible.

As per coding guidelines, "Review test code briefly. Focus on: Test coverage for the feature being tested".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/ui/teams-navigation.test.ts` around lines 11 - 49, Replace
brittle fs string-search assertions with direct module imports and assertions
against the exported route/nav/command structures: import the routes export from
the module that defines App.tsx routes (e.g., the exported routes array or a
getRoutes function) and assert there is an entry with component/name "TeamsView"
and path "/teams"; import the nav items export from SidebarNav (e.g., navItems
or sidebarConfig) and assert an item with label "Teams" has icon "users" and
href or to "/teams"; import the commands export from CommandPalette (e.g.,
commands or paletteItems) and assert a command labeled "Go to Teams" exists with
keybinding "G V" and a navigate handler or route pointing to "/teams". Ensure
tests use the exported symbols (routes, navItems, commands) rather than reading
file text so wiring is verified at runtime.
console/src/ui/viewer/views/Teams/index.tsx (1)

12-19: Deduplicate formatRepoUrl into a shared utility.

This formatter is now repeated across Teams views, which increases drift risk for URL display behavior. Extract to a shared helper and import it in both places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/views/Teams/index.tsx` around lines 12 - 19, The
formatRepoUrl function is duplicated; extract it into a shared helper (e.g.,
export function formatRepoUrl(url: string): string in a new/util file) and
replace the local definition in Teams view(s) with an import of that helper;
update the callers in Teams/index.tsx (and any other Teams view files using
formatRepoUrl) to import the new shared function and remove the local
implementation so URL formatting is centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@console/src/services/worker/http/routes/TeamsRoutes.ts`:
- Around line 457-468: The update-asset handler (handleUpdateAsset) mutates the
lockfile and runs installs without synchronizing with concurrent installs; guard
the critical section by checking and setting the shared flag _isInstalling: at
the start of the handler, if (this._isInstalling) return a 409/conflict
response, otherwise set this._isInstalling = true before performing lockfile
mutation and the install, and ensure you clear it in a finally block so it’s
always reset; apply the same pattern to the other install/update handler
referenced around lines 495-499 (the other route handler that mutates the
lockfile) so both use the same check/set/try...finally/unset sequence and avoid
race conditions.
- Around line 320-324: The catch block in the Teams push handler currently
returns a 200 OK with an error payload; update the error handling to send an
appropriate non-2xx HTTP status (e.g., res.status(500)) before returning the
JSON error, and include the same message and logger.error usage (see the catch
that defines const message = (error as Error).message and calls
logger.error("HTTP", "Teams push failed", { name }, error as Error)); apply the
identical change to the catch blocks in handleRemove, handleInit, and
handleUpdateAsset so failures return a non-2xx status with the same error JSON
and logging behavior.
- Around line 470-494: The current remove-then-add sequence can leave the
lockfile degraded if add/install fails; change the flow in the method that calls
runSxCommand so you add the new asset before removing the old (build addArgs and
call runSxCommand(addArgs, PUSH_TIMEOUT_MS) first), and only on successful add
call runSxCommand([...sxPath, "remove", name, "--version",
String(currentVersion), "--yes"], REMOVE_TIMEOUT_MS) to drop the old entry;
additionally wrap both operations in try/catch and on any failure attempt a
compensating rollback by calling runSxCommand([...sxPath, "add", name,
"--version", String(currentVersion), "--yes"], PUSH_TIMEOUT_MS) to restore the
previous version (use currentVersion, addArgs, runSxCommand, REMOVE_TIMEOUT_MS,
PUSH_TIMEOUT_MS to locate code).
- Around line 282-290: The current prefix check on resolvedSource can be
bypassed via symlinks; change validation to compare real filesystem paths: call
fs.promises.realpath (or fs.realpathSync) for projectRoot and resolvedSource
(referencing projectRoot and resolvedSource variables and path.resolve usage)
and verify the resolvedSourceRealpath equals projectRootRealpath or
startsWith(projectRootRealpath + path.sep) before accepting; return the same 400
error on failure. Ensure to handle realpath errors (treat as invalid) and
preserve use of process.env.CLAUDE_PROJECT_ROOT when computing
projectRootRealpath.

In `@console/src/ui/viewer/components/TeamGate.tsx`:
- Around line 16-18: The overlay wrapper currently uses pointer-events-none
which doesn't prevent keyboard focus; update the wrapper div in TeamGate (the
element rendering {children}) to remove keyboard access by adding accessibility
attributes: set aria-hidden="true" and add the inert attribute (and fallback to
tabIndex={-1} if inert not supported) on that div so underlying interactive
controls are excluded from the accessibility tree and cannot receive keyboard
focus; ensure the same element keeps the visual classes (opacity-30 blur-[2px]
select-none) while adding these attributes.

In `@console/src/ui/viewer/hooks/useStats.ts`:
- Around line 178-183: The loadTeamsStatusData callback currently assigns any
JSON response to teamsStatusData; update loadTeamsStatusData to first verify the
fetch response with if (!res.ok) return or set a safe fallback, then parse JSON
and validate its shape (e.g., confirm expected keys/array via Array.isArray or
typeof checks) before calling setTeamsStatusData; on invalid shape or parse
errors, set a safe default (empty array/object) and handle/log the error in the
catch block so setTeamsStatusData is never called with unexpected payloads.

In `@console/src/ui/viewer/hooks/useTeams.ts`:
- Around line 241-317: The issue: successful mutations do not consistently
refresh Teams state—modify initTeams, pushAsset, and removeAsset to call and
await fetchStatus() when the API returns success (like updateAsset does) so the
UI isn't stale; also update the useCallback dependency arrays for pushAsset and
removeAsset to include fetchStatus so closures reference the latest function.
Ensure each of initTeams, pushAsset, and removeAsset awaits fetchStatus() after
detecting data.success (or after res.ok and parsed success) before returning.

In `@console/src/ui/viewer/views/Teams/TeamsAssetTable.tsx`:
- Around line 163-174: The remove flow in handleRemoveClick can leave isRemoving
true if onRemove rejects; wrap the await onRemove(asset.name) in a try/finally
(or try/catch/finally) so setIsRemoving(false) always runs, and keep the
existing setConfirmRemove(false) behavior; update handleRemoveClick to
setIsRemoving(true) before the try, await onRemove inside the try, and move
setIsRemoving(false) into the finally block so failures won't leave the UI
stuck.

In `@console/src/ui/viewer/views/Teams/TeamsPushPanel.tsx`:
- Around line 70-95: The handlePush function can leave the UI stuck if pushAsset
throws; wrap the per-asset loop in a try/catch and ensure setIsPushing(false)
runs in a finally block. Specifically, in handlePush use try { ... for each
asset await pushAsset(...) ... } catch (err) { record the error into
localResults for the failing asset (set success: false and error: String(err))
and update setPushResults } finally { setIsPushing(false) } and only call
onPushComplete when allSucceeded remains true after the try/catch; reference
handlePush, pushAsset, setIsPushing, setPushResults, localResults, and
onPushComplete when making the changes.
- Around line 40-48: The effect in TeamsPushPanel that calls discover() doesn't
handle promise rejection, leaving isLoading true on errors; update the useEffect
so the async call to discover() is awaited inside a try/catch/finally (or add a
.catch/.finally chain) to ensure setIsLoading(false) runs on both success and
failure, still guarding with the mounted flag before calling
setAssets/setRepoUrl; ensure any error is swallowed or logged (e.g., via
console.error or an existing logger) and that the cleanup function sets mounted
= false as before.

In `@console/src/ui/viewer/views/Teams/TeamsSetupTab.tsx`:
- Around line 24-35: handleInit currently calls initTeams directly so if
initTeams throws the async error the UI stays stuck with
setIsInitializing(true); wrap the initTeams call in a try/catch/finally inside
handleInit: in try await initTeams(...); in catch setInitError to the caught
error message (or a friendly fallback) and ensure setIsReconfiguring/setRepoUrl
behavior only on success; in finally always call setIsInitializing(false) so the
Initialize button is never left disabled. Reference symbols: handleInit,
initTeams, setIsInitializing, setInitError, setIsReconfiguring, setRepoUrl.

In `@console/tests/hooks/use-teams.test.ts`:
- Around line 166-179: Replace the brittle source-string assertions in the test
with behavioral tests that mock global.fetch and exercise the hook's exported
actions (discover, pushAsset, removeAsset, initTeams, updateAsset) from
src/ui/viewer/hooks/useTeams.ts: for each action, mock fetch responses and
assert the correct endpoint, HTTP method, and request body were used, then
verify the hook's state/mutation calls (e.g., that isInstalling toggles,
mergeAssets is called with returned assets, removeAsset/updateAsset cause
expected state changes, and initTeams triggers the init endpoint and subsequent
refresh); use jest.fn() or a fetch-mock and clear/reset mocks between tests to
ensure isolation and meaningful failure signals.

In `@console/tests/ui/teams-install.test.ts`:
- Around line 4-71: The current tests only assert source text; add a runtime
behavior test in console/tests/ui/teams-install.test.ts that renders the Teams
view (importing the component from src/ui/viewer/views/Teams/index.tsx), mocks
the sync API and/or the internal sync handler (e.g., handleInstall or
TeamsSyncButton click), simulates a user click on the "Sync All" button, and
asserts that the useToast methods (.success, .error, .warning) are invoked for
success, error, and timeout scenarios; use jest.fn() or your existing test
helpers to spy on the toast returned by useToast() and stub network calls to
drive each outcome.

In `@console/tests/ui/teams-view.test.ts`:
- Around line 87-143: Add interaction tests for TeamsAssetTable that mount the
component (instead of only SSR render) and assert the onUpdate and onRemove
callbacks are invoked through user interactions: locate and click the "Update"
button and verify onUpdate was called; locate and click the "Remove" button,
drive the confirmation dialog flow (click confirm) and verify onRemove was
called; and for uninstalled vault assets locate and click "Install" and verify
appropriate install handler or onUpdate is invoked. Use the same props pattern
from the existing tests, pass jest.fn() spies for onUpdate/onRemove, render
TeamsAssetTable (imported from TeamsAssetTable.js) with tier "team" and "solo"
scenarios, simulate clicks with a DOM testing utility (e.g.,
`@testing-library/react` + userEvent), and assert the spies are called and the
remove-confirmation path triggers onRemove only after confirm.

In `@console/tests/worker/teams-routes.test.ts`:
- Around line 19-37: Add behavior tests for the POST /api/teams/update-asset
route (in addition to the existing registration test) that exercise validation,
command sequencing, and failure paths: instantiate TeamsRoutes, capture the
registered POST handler for "/api/teams/update-asset" (same pattern used to
capture registrations), then call that handler directly with mocked req/res
objects; add separate tests to assert (1) invalid input returns the expected 4xx
validation response, (2) valid input triggers the expected sequence of
operations by mocking and spying the lockfile/installer services (assert call
order and payloads), and (3) when a mocked dependency throws, the handler
returns the correct error status and logs/response; use the same registered
capture mechanism and mock services used elsewhere in this test file to keep
tests focused and isolated.

In `@docs/site/src/pages/docs/TeamsSection.tsx`:
- Around line 10-16: The docs still reference Vault/Roles terminology and the
removed commands; update all occurrences of "Vault", "vault settings", "Vault
Setup" and wording like "manage roles" to the new Teams terminology (e.g.,
"Team" or "Teams") and replace deprecated CLI commands such as `sx vault show`
with the current Teams equivalents (e.g., `sx teams show`), and remove any
instructions about role management flow; update the strings in TeamsSection.tsx
(e.g., the "Vault Setup" and "Push & Pull" entries) and the other affected
ranges (around lines 85-89 and 176-177) to use the new Team/Teams naming and
capabilities.

---

Minor comments:
In `@console/src/ui/viewer/components/CommandPalette.tsx`:
- Around line 124-132: Arrow-key handlers in CommandPalette.tsx compute new
selected index using modulo which yields NaN when filteredCommands.length === 0;
add a guard in the ArrowDown and ArrowUp cases (or before invoking
setSelectedIndex) to no-op when filteredCommands.length === 0 so
setSelectedIndex is not called with invalid math, keeping the current selection
unchanged (or explicitly set to a safe default like -1/0 if that state is
handled elsewhere).

In `@console/src/ui/viewer/views/Dashboard/TeamsStatus.tsx`:
- Around line 68-71: The availability check in TeamsStatus.tsx builds
installedNames from assets using name-only and computes availableCount by
filtering catalog by name, which breaks when assets share names across types;
update the logic to use a composite key (e.g., `${type}::${name}`) for both
installedNames and the catalog filter so matching is done on type+name (ensure
you reference the installedNames set and availableCount calculation), and handle
possible missing/undefined type values consistently when building the composite
keys.

In `@console/src/ui/viewer/views/Teams/index.tsx`:
- Around line 41-42: The Team gating logic is using license which can be null
while the license is still loading; update the code that calls useLicense() to
also read isLoading and defer rendering any TeamGate checks until loading
settles (for example, return a loader or skip rendering TeamGate when isLoading
is true), and then pass the resolved license/tier (or undefined) into TeamGate
instead of null so eligible users don't briefly see locked UI; apply the same
change to all TeamGate usages referenced (the blocks around where useLicense()
is read and TeamGate is rendered).

In `@console/src/ui/viewer/views/Teams/TeamsSetupTab.tsx`:
- Around line 66-71: In TeamsSetupTab (the placeholder ternary using repoType),
replace legacy "vault" wording in the user-facing placeholders: change
"git@github.com:org/team-vault.git" to a non-vault name (e.g.,
"git@github.com:org/team-repo.git") and change "/path/to/vault" to a matching
local path (e.g., "/path/to/team-repo" or "/path/to/teams") so the placeholders
reflect the Teams rename and remain consistent with the UI copy.

In `@console/src/ui/viewer/views/Teams/TeamsSummaryCards.tsx`:
- Around line 49-54: The stat description in TeamsSummaryCards.tsx (the elements
with classNames stat-title, stat-value, stat-desc that display commands and
other via variables commands and other) can become misleading when other > 0;
update the rendering logic for the stat-desc so it conditionally matches the
displayed metric (e.g., when other > 0 show a description like "Slash commands
and other" or similar, otherwise keep "Slash commands") so the description
aligns with the title/value.

In `@console/tests/ui/teams-view.test.ts`:
- Line 116: Rename the test title string in teams-view.test.ts from "renders
install button for uninstalled vault assets" to "renders install button for
uninstalled team assets" so the test name matches the Teams terminology; locate
the it(...) call in the file (the test case starting with it("renders install
button for uninstalled vault assets", ...) and update only the descriptive
string, leaving the test body and assertions unchanged.

In `@README.md`:
- Line 315: The README still uses legacy "Vault" terminology and routes (e.g.,
occurrences of "/vault", "Vault" view, and the legacy file reference
`team-vault.md`) which should be migrated to the new canonical "teams" naming;
search for and replace all references of "/vault" with "/teams", rename mentions
of "Vault" view to "Teams" view (preserving context/capitalization), and update
or remove `team-vault.md` references to the new `teams` documentation or
filename so the docs consistently reflect the product naming.

---

Nitpick comments:
In `@console/src/ui/viewer/views/Teams/index.tsx`:
- Around line 12-19: The formatRepoUrl function is duplicated; extract it into a
shared helper (e.g., export function formatRepoUrl(url: string): string in a
new/util file) and replace the local definition in Teams view(s) with an import
of that helper; update the callers in Teams/index.tsx (and any other Teams view
files using formatRepoUrl) to import the new shared function and remove the
local implementation so URL formatting is centralized.

In `@console/tests/hooks/useSettings.test.ts`:
- Around line 47-53: The test currently checks only presence of six expected
commands and absence of 'vault', so update the assertion to verify the exact set
of keys in DEFAULT_SETTINGS.commands to prevent accidental additions; in the
test for DEFAULT_SETTINGS (in useSettings.test.ts) import DEFAULT_SETTINGS and
replace the per-key checks with a single assertion that
Object.keys(DEFAULT_SETTINGS.commands) (sorted) strictly equals the expected
array (sorted) — referencing DEFAULT_SETTINGS and its commands property to
locate the code to change.

In `@console/tests/ui/teams-navigation.test.ts`:
- Around line 11-49: Replace brittle fs string-search assertions with direct
module imports and assertions against the exported route/nav/command structures:
import the routes export from the module that defines App.tsx routes (e.g., the
exported routes array or a getRoutes function) and assert there is an entry with
component/name "TeamsView" and path "/teams"; import the nav items export from
SidebarNav (e.g., navItems or sidebarConfig) and assert an item with label
"Teams" has icon "users" and href or to "/teams"; import the commands export
from CommandPalette (e.g., commands or paletteItems) and assert a command
labeled "Go to Teams" exists with keybinding "G V" and a navigate handler or
route pointing to "/teams". Ensure tests use the exported symbols (routes,
navItems, commands) rather than reading file text so wiring is verified at
runtime.

In `@console/tests/worker/teams-routes.test.ts`:
- Around line 168-190: The test calls TeamsRoutes.setupRoutes then invokes the
discover handler and uses a sleep to wait; instead, mock external FS/Git
interactions used by TeamsRoutes (e.g., any fs, git, or repo helpers it depends
on) using jest mocks/spies and call the route handler as an async function so
you can await it directly; locate the route via TeamsRoutes.setupRoutes and the
discoverHandler reference, provide a fakeReq/fakeRes that returns a Promise from
json/status (or make the handler return a Promise) and then await
discoverHandler(fakeReq, fakeRes) instead of using setTimeout to make the test
deterministic and avoid touching the real filesystem or git.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec21700 and 94cb42c.

⛔ Files ignored due to path filters (9)
  • docs/site/api/events/ingest.ts is excluded by !docs/site/api/**
  • docs/site/api/portal/session.ts is excluded by !docs/site/api/**
  • docs/site/api/trial/heartbeat.ts is excluded by !docs/site/api/**
  • docs/site/api/trial/start.ts is excluded by !docs/site/api/**
  • pilot/rules/team-vault.md is excluded by !pilot/**
  • pilot/scripts/worker-service.cjs is excluded by !pilot/**
  • pilot/settings.json is excluded by !pilot/**
  • pilot/ui/viewer-bundle.js is excluded by !pilot/**
  • pilot/ui/viewer.css is excluded by !pilot/**
📒 Files selected for processing (38)
  • README.md
  • console/src/services/worker-service.ts
  • console/src/services/worker/http/routes/SettingsRoutes.ts
  • console/src/services/worker/http/routes/TeamsRoutes.ts
  • console/src/services/worker/http/routes/VaultRoutes.ts
  • console/src/ui/viewer/App.tsx
  • console/src/ui/viewer/components/CommandPalette.tsx
  • console/src/ui/viewer/components/TeamGate.tsx
  • console/src/ui/viewer/constants/shortcuts.ts
  • console/src/ui/viewer/hooks/useSettings.ts
  • console/src/ui/viewer/hooks/useStats.ts
  • console/src/ui/viewer/hooks/useTeams.ts
  • console/src/ui/viewer/layouts/Sidebar/SidebarNav.tsx
  • console/src/ui/viewer/views/Dashboard/TeamsStatus.tsx
  • console/src/ui/viewer/views/Dashboard/index.tsx
  • console/src/ui/viewer/views/Teams/TeamsAssetDetail.tsx
  • console/src/ui/viewer/views/Teams/TeamsAssetTable.tsx
  • console/src/ui/viewer/views/Teams/TeamsPushPanel.tsx
  • console/src/ui/viewer/views/Teams/TeamsSetupTab.tsx
  • console/src/ui/viewer/views/Teams/TeamsSummaryCards.tsx
  • console/src/ui/viewer/views/Teams/index.tsx
  • console/src/ui/viewer/views/Vault/index.tsx
  • console/src/ui/viewer/views/index.ts
  • console/tests/hooks/use-teams.test.ts
  • console/tests/hooks/useSettings.test.ts
  • console/tests/settings-routes.test.ts
  • console/tests/ui/SidebarNav.test.ts
  • console/tests/ui/search-removal.test.ts
  • console/tests/ui/teams-install.test.ts
  • console/tests/ui/teams-navigation.test.ts
  • console/tests/ui/teams-view.test.ts
  • console/tests/ui/vault-install.test.ts
  • console/tests/ui/vault-navigation.test.ts
  • console/tests/ui/vault-view.test.ts
  • console/tests/ui/views-index.test.ts
  • console/tests/worker/teams-routes.test.ts
  • docs/site/src/pages/docs/TeamsSection.tsx
  • installer/steps/finalize.py
💤 Files with no reviewable changes (9)
  • console/src/ui/viewer/hooks/useSettings.ts
  • console/tests/ui/vault-install.test.ts
  • console/tests/settings-routes.test.ts
  • console/src/services/worker/http/routes/SettingsRoutes.ts
  • console/tests/ui/vault-view.test.ts
  • console/tests/ui/vault-navigation.test.ts
  • console/src/services/worker/http/routes/VaultRoutes.ts
  • console/src/ui/viewer/views/Vault/index.tsx
  • installer/steps/finalize.py

Comment on lines +282 to +290
const projectRoot = process.env.CLAUDE_PROJECT_ROOT || process.cwd();
const resolvedSource = path.resolve(projectRoot, source);
if (
resolvedSource !== projectRoot &&
!resolvedSource.startsWith(projectRoot + path.sep)
) {
res.status(400).json({ error: "Source path must be within project" });
return;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Source-path validation is vulnerable to symlink escape.

Prefix checks on path.resolve(...) do not prevent paths that traverse outside the project via symlinks.

🔒 Suggested hardening
+import { readdirSync, existsSync, realpathSync } from "fs";
...
       const projectRoot = process.env.CLAUDE_PROJECT_ROOT || process.cwd();
       const resolvedSource = path.resolve(projectRoot, source);
+      let realProjectRoot: string;
+      let realSource: string;
+      try {
+        realProjectRoot = realpathSync(projectRoot);
+        realSource = realpathSync(resolvedSource);
+      } catch {
+        res.status(400).json({ error: "Source path is invalid or does not exist" });
+        return;
+      }
       if (
-        resolvedSource !== projectRoot &&
-        !resolvedSource.startsWith(projectRoot + path.sep)
+        realSource !== realProjectRoot &&
+        !realSource.startsWith(realProjectRoot + path.sep)
       ) {
         res.status(400).json({ error: "Source path must be within project" });
         return;
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const projectRoot = process.env.CLAUDE_PROJECT_ROOT || process.cwd();
const resolvedSource = path.resolve(projectRoot, source);
if (
resolvedSource !== projectRoot &&
!resolvedSource.startsWith(projectRoot + path.sep)
) {
res.status(400).json({ error: "Source path must be within project" });
return;
}
const projectRoot = process.env.CLAUDE_PROJECT_ROOT || process.cwd();
const resolvedSource = path.resolve(projectRoot, source);
let realProjectRoot: string;
let realSource: string;
try {
realProjectRoot = realpathSync(projectRoot);
realSource = realpathSync(resolvedSource);
} catch {
res.status(400).json({ error: "Source path is invalid or does not exist" });
return;
}
if (
realSource !== realProjectRoot &&
!realSource.startsWith(realProjectRoot + path.sep)
) {
res.status(400).json({ error: "Source path must be within project" });
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/TeamsRoutes.ts` around lines 282 -
290, The current prefix check on resolvedSource can be bypassed via symlinks;
change validation to compare real filesystem paths: call fs.promises.realpath
(or fs.realpathSync) for projectRoot and resolvedSource (referencing projectRoot
and resolvedSource variables and path.resolve usage) and verify the
resolvedSourceRealpath equals projectRootRealpath or
startsWith(projectRootRealpath + path.sep) before accepting; return the same 400
error on failure. Ensure to handle realpath errors (treat as invalid) and
preserve use of process.env.CLAUDE_PROJECT_ROOT when computing
projectRootRealpath.

Comment on lines +320 to +324
} catch (error) {
const message = (error as Error).message || "Push failed";
logger.error("HTTP", "Teams push failed", { name }, error as Error);
res.json({ success: false, error: message });
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Failures are returned as HTTP 200 on mutating endpoints.

Returning 200 for operational failures makes monitoring/retries and client handling ambiguous. Use non-2xx status codes for failed mutations.

🔧 Suggested response semantics
-        res.json({ success: false, error: message });
+        res.status(502).json({ success: false, error: message });

Apply the same pattern in handleRemove, handleInit, and handleUpdateAsset.

Also applies to: 355-358, 394-397, 503-512

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/TeamsRoutes.ts` around lines 320 -
324, The catch block in the Teams push handler currently returns a 200 OK with
an error payload; update the error handling to send an appropriate non-2xx HTTP
status (e.g., res.status(500)) before returning the JSON error, and include the
same message and logger.error usage (see the catch that defines const message =
(error as Error).message and calls logger.error("HTTP", "Teams push failed", {
name }, error as Error)); apply the identical change to the catch blocks in
handleRemove, handleInit, and handleUpdateAsset so failures return a non-2xx
status with the same error JSON and logging behavior.

Comment on lines +457 to +468
private handleUpdateAsset = this.wrapHandler(
async (req: Request, res: Response): Promise<void> => {
const { name, currentVersion, scope, scopeUrl } = req.body;
if (!name || !NAME_REGEX.test(name)) {
res.status(400).json({ error: "Invalid asset name" });
return;
}
const sxPath = this.resolveSxBinary();
if (!sxPath) {
res.status(500).json({ error: "sx CLI not found" });
return;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

update-asset is not synchronized with ongoing installs.

This endpoint performs lockfile mutation plus install but does not check/set _isInstalling, so concurrent install/update requests can race and corrupt state.

🧵 Suggested synchronization fix
   private handleUpdateAsset = this.wrapHandler(
     async (req: Request, res: Response): Promise<void> => {
+      if (this._isInstalling) {
+        res.status(409).json({ error: "Installation already in progress" });
+        return;
+      }
+      this._isInstalling = true;
+      this.statusCache = null;
+
       const { name, currentVersion, scope, scopeUrl } = req.body;
       if (!name || !NAME_REGEX.test(name)) {
         res.status(400).json({ error: "Invalid asset name" });
+        this._isInstalling = false;
         return;
       }
...
       try {
         // Remove old version from lock file
...
         await this.runSxCommand(
           [sxPath, "install", "--repair", "--target", projectRoot],
           INSTALL_TIMEOUT_MS,
         );
         this.statusCache = null;
         this.detailCache.clear();
         res.json({ success: true, error: null });
       } catch (error) {
...
         res.json({ success: false, error: message });
+      } finally {
+        this._isInstalling = false;
       }
     },
   );

Also applies to: 495-499

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/TeamsRoutes.ts` around lines 457 -
468, The update-asset handler (handleUpdateAsset) mutates the lockfile and runs
installs without synchronizing with concurrent installs; guard the critical
section by checking and setting the shared flag _isInstalling: at the start of
the handler, if (this._isInstalling) return a 409/conflict response, otherwise
set this._isInstalling = true before performing lockfile mutation and the
install, and ensure you clear it in a finally block so it’s always reset; apply
the same pattern to the other install/update handler referenced around lines
495-499 (the other route handler that mutates the lockfile) so both use the same
check/set/try...finally/unset sequence and avoid race conditions.

Comment on lines +470 to +494
// Remove old version from lock file
if (currentVersion) {
await this.runSxCommand(
[
sxPath,
"remove",
name,
"--version",
String(currentVersion),
"--yes",
],
REMOVE_TIMEOUT_MS,
);
}
// Re-add with scope (picks up latest version)
const addArgs = [sxPath, "add", name, "--yes"];
if (scope === "global") {
addArgs.push("--scope-global");
} else if (scopeUrl) {
addArgs.push("--scope-repo", scopeUrl);
} else {
addArgs.push("--scope-global");
}
await this.runSxCommand(addArgs, PUSH_TIMEOUT_MS);
// Install the updated version
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove-first update flow can leave the asset missing on partial failure.

If remove succeeds and add or install fails, the lockfile is left in a degraded state. Add a compensating rollback or use an update sequence that commits only after success.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/services/worker/http/routes/TeamsRoutes.ts` around lines 470 -
494, The current remove-then-add sequence can leave the lockfile degraded if
add/install fails; change the flow in the method that calls runSxCommand so you
add the new asset before removing the old (build addArgs and call
runSxCommand(addArgs, PUSH_TIMEOUT_MS) first), and only on successful add call
runSxCommand([...sxPath, "remove", name, "--version", String(currentVersion),
"--yes"], REMOVE_TIMEOUT_MS) to drop the old entry; additionally wrap both
operations in try/catch and on any failure attempt a compensating rollback by
calling runSxCommand([...sxPath, "add", name, "--version",
String(currentVersion), "--yes"], PUSH_TIMEOUT_MS) to restore the previous
version (use currentVersion, addArgs, runSxCommand, REMOVE_TIMEOUT_MS,
PUSH_TIMEOUT_MS to locate code).

Comment on lines +16 to +18
<div className="opacity-30 pointer-events-none blur-[2px] select-none">
{children}
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Locked content remains keyboard-focusable behind the overlay.

pointer-events-none only blocks pointer interaction. Underlying focusable controls can still be reached via keyboard/assistive tech.

🔧 Suggested hardening
-      <div className="opacity-30 pointer-events-none blur-[2px] select-none">
+      <div
+        inert
+        aria-hidden="true"
+        className="opacity-30 pointer-events-none blur-[2px] select-none"
+      >
         {children}
       </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="opacity-30 pointer-events-none blur-[2px] select-none">
{children}
</div>
<div
inert
aria-hidden="true"
className="opacity-30 pointer-events-none blur-[2px] select-none"
>
{children}
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/src/ui/viewer/components/TeamGate.tsx` around lines 16 - 18, The
overlay wrapper currently uses pointer-events-none which doesn't prevent
keyboard focus; update the wrapper div in TeamGate (the element rendering
{children}) to remove keyboard access by adding accessibility attributes: set
aria-hidden="true" and add the inert attribute (and fallback to tabIndex={-1} if
inert not supported) on that div so underlying interactive controls are excluded
from the accessibility tree and cannot receive keyboard focus; ensure the same
element keeps the visual classes (opacity-30 blur-[2px] select-none) while
adding these attributes.

Comment on lines 166 to 179
it("source contains API endpoint fetch logic", async () => {
const fs = await import("fs");
const source = fs.readFileSync("src/ui/viewer/hooks/useVault.ts", "utf-8");
const source = fs.readFileSync("src/ui/viewer/hooks/useTeams.ts", "utf-8");

expect(source).toContain("/api/vault/status");
expect(source).toContain("/api/vault/detail/");
expect(source).toContain("/api/vault/install");
expect(source).toContain("/api/teams/status");
expect(source).toContain("/api/teams/detail/");
expect(source).toContain("/api/teams/install");
expect(source).toContain("isInstalling");
expect(source).toContain("mergeAssets");
expect(source).toContain("/api/teams/remove");
expect(source).toContain("removeAsset");
expect(source).toContain("/api/teams/init");
expect(source).toContain("initTeams");
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace source-string assertions with behavioral fetch-mocked tests for Teams mutations.

This block can pass even when runtime behavior regresses (wrong method/body, missing refresh, wrong endpoint usage). For this PR, add behavior tests around discover, pushAsset, removeAsset, initTeams, and updateAsset using mocked fetch.

As per coding guidelines, "/tests/: Review test code briefly. Focus on: Test coverage for the feature being tested; Proper mocking of external dependencies; Clear test names that describe behavior**."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/hooks/use-teams.test.ts` around lines 166 - 179, Replace the
brittle source-string assertions in the test with behavioral tests that mock
global.fetch and exercise the hook's exported actions (discover, pushAsset,
removeAsset, initTeams, updateAsset) from src/ui/viewer/hooks/useTeams.ts: for
each action, mock fetch responses and assert the correct endpoint, HTTP method,
and request body were used, then verify the hook's state/mutation calls (e.g.,
that isInstalling toggles, mergeAssets is called with returned assets,
removeAsset/updateAsset cause expected state changes, and initTeams triggers the
init endpoint and subsequent refresh); use jest.fn() or a fetch-mock and
clear/reset mocks between tests to ensure isolation and meaningful failure
signals.

Comment on lines +4 to +71
it('TeamsSyncButton component exists and renders correctly', async () => {
const fs = await import('fs/promises');
const teamsViewContent = await fs.readFile(
'src/ui/viewer/views/Teams/index.tsx',
'utf-8'
);

expect(teamsViewContent).toContain('function TeamsSyncButton');

expect(teamsViewContent).toMatch(/className="btn btn-primary btn-sm"/);

expect(teamsViewContent).toContain('loading loading-spinner');
expect(teamsViewContent).toContain('Syncing...');

expect(teamsViewContent).toContain('Sync All');
});

it('TeamsView uses useToast for notifications', async () => {
const fs = await import('fs/promises');
const teamsViewContent = await fs.readFile(
'src/ui/viewer/views/Teams/index.tsx',
'utf-8'
);

expect(teamsViewContent).toMatch(/import.*useToast.*from.*ToastContext/);

expect(teamsViewContent).toMatch(/useToast\(\)/);
});

it('handles successful install with toast notification', async () => {
const fs = await import('fs/promises');
const teamsViewContent = await fs.readFile(
'src/ui/viewer/views/Teams/index.tsx',
'utf-8'
);

expect(teamsViewContent).toMatch(/\.success\(['"].*[Tt]eams.*sync/i);
});

it('handles install error with toast notification', async () => {
const fs = await import('fs/promises');
const teamsViewContent = await fs.readFile(
'src/ui/viewer/views/Teams/index.tsx',
'utf-8'
);

expect(teamsViewContent).toMatch(/\.error\(/);
});

it('handles install timeout with warning toast', async () => {
const fs = await import('fs/promises');
const teamsViewContent = await fs.readFile(
'src/ui/viewer/views/Teams/index.tsx',
'utf-8'
);

expect(teamsViewContent).toMatch(/\.warning\(/);
});

it('handleInstall wrapper function exists', async () => {
const fs = await import('fs/promises');
const teamsViewContent = await fs.readFile(
'src/ui/viewer/views/Teams/index.tsx',
'utf-8'
);

expect(teamsViewContent).toMatch(/handle(Install|Sync)/);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

These tests validate source text, not install behavior.

All assertions are string/regex checks on file content, so they can still pass if sync handlers or toast wiring break at runtime. Add at least one behavior-level test that renders Teams view, triggers sync, and asserts success/error/timeout toast calls.

As per coding guidelines, "Review test code briefly. Focus on: Test coverage for the feature being tested".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/ui/teams-install.test.ts` around lines 4 - 71, The current
tests only assert source text; add a runtime behavior test in
console/tests/ui/teams-install.test.ts that renders the Teams view (importing
the component from src/ui/viewer/views/Teams/index.tsx), mocks the sync API
and/or the internal sync handler (e.g., handleInstall or TeamsSyncButton click),
simulates a user click on the "Sync All" button, and asserts that the useToast
methods (.success, .error, .warning) are invoked for success, error, and timeout
scenarios; use jest.fn() or your existing test helpers to spy on the toast
returned by useToast() and stub network calls to drive each outcome.

Comment on lines +87 to +143
it("renders action buttons for installed assets with update", async () => {
const { TeamsAssetTable } = await import("../../src/ui/viewer/views/Teams/TeamsAssetTable.js");

const assets = [
{ name: "my-skill", type: "skill", latestVersion: "3", versionsCount: 3, installedVersion: "v2", installed: true, hasUpdate: true, scope: "Global", clients: [] },
];

const html = renderToString(
React.createElement(TeamsAssetTable, {
assets,
searchQuery: "",
activeTab: "all",
onTabChange: () => {},
onSearchChange: () => {},
expandedAsset: null,
onAssetClick: () => {},
fetchDetail: () => Promise.resolve(),
detailCache: new Map(),
loadingDetails: new Set<string>(),
tier: "team",
onUpdate: () => {},
onRemove: () => Promise.resolve(),
}),
);

expect(html).toContain("Update");
expect(html).toContain("Remove");
});

it("renders install button for uninstalled vault assets", async () => {
const { TeamsAssetTable } = await import("../../src/ui/viewer/views/Teams/TeamsAssetTable.js");

const assets = [
{ name: "my-rule", type: "rule", latestVersion: "1", versionsCount: 1, installedVersion: null, installed: false, hasUpdate: false, scope: null, clients: [] },
];

const html = renderToString(
React.createElement(TeamsAssetTable, {
assets,
searchQuery: "",
activeTab: "all",
onTabChange: () => {},
onSearchChange: () => {},
expandedAsset: null,
onAssetClick: () => {},
fetchDetail: () => Promise.resolve(),
detailCache: new Map(),
loadingDetails: new Set<string>(),
tier: "solo",
onUpdate: () => {},
onRemove: () => Promise.resolve(),
}),
);

expect(html).toContain("Install");
expect(html).not.toContain("Remove");
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

CRUD action coverage is incomplete for TeamsAssetTable.

These tests only check button labels in SSR output. Please add interaction-level tests that verify onUpdate/onRemove are invoked correctly, including the remove confirmation path.

As per coding guidelines, "Review test code briefly. Focus on: Test coverage for the feature being tested".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/ui/teams-view.test.ts` around lines 87 - 143, Add interaction
tests for TeamsAssetTable that mount the component (instead of only SSR render)
and assert the onUpdate and onRemove callbacks are invoked through user
interactions: locate and click the "Update" button and verify onUpdate was
called; locate and click the "Remove" button, drive the confirmation dialog flow
(click confirm) and verify onRemove was called; and for uninstalled vault assets
locate and click "Install" and verify appropriate install handler or onUpdate is
invoked. Use the same props pattern from the existing tests, pass jest.fn()
spies for onUpdate/onRemove, render TeamsAssetTable (imported from
TeamsAssetTable.js) with tier "team" and "solo" scenarios, simulate clicks with
a DOM testing utility (e.g., `@testing-library/react` + userEvent), and assert the
spies are called and the remove-confirmation path triggers onRemove only after
confirm.

Comment on lines +19 to +37
it("registers all 8 routes", () => {
const routes = new TeamsRoutes();
const registered: { method: string; path: string }[] = [];
const fakeApp = {
get: (path: string, _handler: any) => registered.push({ method: "GET", path }),
post: (path: string, _handler: any) => registered.push({ method: "POST", path }),
};
routes.setupRoutes(fakeApp as any);

expect(registered).toContainEqual({ method: "GET", path: "/api/vault/status" });
expect(registered).toContainEqual({ method: "POST", path: "/api/vault/install" });
expect(registered).toContainEqual({ method: "GET", path: "/api/vault/detail/:name" });
expect(registered).toContainEqual({ method: "GET", path: "/api/teams/status" });
expect(registered).toContainEqual({ method: "POST", path: "/api/teams/install" });
expect(registered).toContainEqual({ method: "GET", path: "/api/teams/detail/:name" });
expect(registered).toContainEqual({ method: "POST", path: "/api/teams/push" });
expect(registered).toContainEqual({ method: "POST", path: "/api/teams/remove" });
expect(registered).toContainEqual({ method: "POST", path: "/api/teams/init" });
expect(registered).toContainEqual({ method: "GET", path: "/api/teams/discover" });
expect(registered).toContainEqual({ method: "POST", path: "/api/teams/update-asset" });
expect(registered.length).toBe(8);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

/api/teams/update-asset is only registration-tested, not behavior-tested.

Given it mutates lockfile/install state, add endpoint behavior tests (validation, command sequencing, and failure-path expectations).

As per coding guidelines, "**/tests/**: Review test code briefly. Focus on: Test coverage for the feature being tested".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@console/tests/worker/teams-routes.test.ts` around lines 19 - 37, Add behavior
tests for the POST /api/teams/update-asset route (in addition to the existing
registration test) that exercise validation, command sequencing, and failure
paths: instantiate TeamsRoutes, capture the registered POST handler for
"/api/teams/update-asset" (same pattern used to capture registrations), then
call that handler directly with mocked req/res objects; add separate tests to
assert (1) invalid input returns the expected 4xx validation response, (2) valid
input triggers the expected sequence of operations by mocking and spying the
lockfile/installer services (assert call order and payloads), and (3) when a
mocked dependency throws, the handler returns the correct error status and
logs/response; use the same registered capture mechanism and mock services used
elsewhere in this test file to keep tests focused and isolated.

# Conflicts:
#	pilot/scripts/worker-service.cjs
…ved asset management

- Replace Sync All button with background auto-sync on page load
- Add Teams onboarding screen for first-time sx setup
- Add Asset Types help modal with format reference
- Add project slug prefix convention for /sync and /learn commands
- Add modified indicator (git diff) and Push New Version action
- Improve sx error messages with parseSxError
- Add force refresh param to status endpoint
- Gate all actions behind team/trial tier
- Replace green checkmark with styled check badge for on-disk assets
- Remove rename feature (sx lacks native support)
- Remove /vault command (replaced by Teams dashboard)
- Remove hooks and MCP from Teams (focus on rules, skills, commands, agents)
- Filter out sx metadata .toml files from discover endpoint
- Disable non-claude-code sx clients in installer
- Add GIT_OPTIONAL_LOCKS=0 to all console git operations
- Add distinct badge colors per asset type
- Update all /vault references across docs, README, and blog posts
…top guard and plan questions

From stash@{0} (WIP on dev: 6a18f80):
- Remove legacy 'standard'/'enterprise' tier names from installer, formatter, tests
- Simplify tier checks in statusline formatter (== instead of 'in' tuple)
- Remove 'vault' model config row from Settings page
- Add git-crypt for console directory in .gitattributes
- Update PricingSection with Teams Dashboard features
- Auto-merged launcher auth, banner, cli, trial, widgets changes
- Add Teams row to README commands table
- Fix duplicate .gitattributes entry
- Clean up stray conflict marker in sync.md
@maxritter maxritter changed the title feat: overhaul Teams dashboard with flat UI and CRUD operations feat: Teams asset sharing dashboard, remove Claude Code installation, docs and pricing updates Mar 5, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (6)
.coderabbit.yaml (1)

23-26: Comment no longer accurately describes all exclusions.

The comment on line 24 states these filters are for "git-crypt encrypted directories," but console/** is the active Teams UI codebase (with 1033+ passing tests per PR description), not an encrypted directory. Consider updating the comment to reflect the actual reason for excluding console, or splitting the filters with separate comments.

📝 Suggested comment update
   path_filters:
-    # Exclude git-crypt encrypted directories from reviews (see .gitattributes)
+    # Exclude git-crypt encrypted directories (see .gitattributes) and console UI
     - "!launcher/**"
     - "!console/**"

Alternatively, group with separate comments:

   path_filters:
     # Exclude git-crypt encrypted directories from reviews (see .gitattributes)
     - "!launcher/**"
+    # Exclude console UI (reviewed separately or via other tooling)
+    - "!console/**"
     - "!pilot/**"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.coderabbit.yaml around lines 23 - 26, Update the inaccurate comment above
the path_filters entry: change or split the comment that currently says "Exclude
git-crypt encrypted directories from reviews (see .gitattributes)" so it no
longer implies console/** is encrypted; either revise the single comment to
reflect the actual intent (e.g., specify launcher/** is git-crypt encrypted and
console/** is the active Teams UI excluded for another reason) or add two
separate comments immediately above the respective filters referencing
"!launcher/**" and "!console/**" to clarify each exclusion's purpose.
docs/site/src/components/WorkflowSteps.tsx (1)

195-204: Consider whether Teams belongs in "All Commands" section.

Teams is no longer a slash command (as /vault was removed), yet it's listed under "All Commands". Additionally, the label uses <span> while other items use <code>, creating visual inconsistency.

Options to consider:

  1. Rename the section to something like "Features" or "Tools" to encompass both commands and navigation destinations.
  2. Keep it as-is if the intent is to show all major entry points regardless of invocation method.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/site/src/components/WorkflowSteps.tsx` around lines 195 - 204, Teams is
rendered as a command in WorkflowSteps.tsx but is not a slash command and also
uses a <span> for the label while other items use <code>, causing semantic and
visual inconsistency; update the component by either moving this block out of
the "All Commands" group into a more generic "Features" or "Tools" section (or
rename the section) or keep it but make its presentation consistent by replacing
the <span className="text-sm font-medium text-primary">Teams</span> with the
same <code> element used by other entries so visuals match; ensure you update
any section title text (e.g., "All Commands") and surrounding grouping logic in
WorkflowSteps to reflect the chosen approach.
installer/ui.py (1)

163-165: Minor: Redundant fallback in tier_map.get().

Since line 163 guarantees tier in ("solo", "team"), the fallback tier.title() in line 165 will never execute—the key is always present in tier_map. You can simplify to a direct lookup.

♻️ Suggested simplification
         if tier in ("solo", "team"):
             tier_map = {"solo": "Solo", "team": "Team"}
-            tier_display = tier_map.get(tier, tier.title())
+            tier_display = tier_map[tier]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@installer/ui.py` around lines 163 - 165, The code redundantly uses
tier_map.get(tier, tier.title()) even though the preceding conditional
guarantees tier in ("solo", "team"); change the lookup to a direct dictionary
index (e.g., tier_map[tier]) when assigning tier_display and remove the
unreachable fallback expression so tier_map and tier_display use a simple
deterministic lookup for the tier variable.
README.md (1)

49-51: Consider removing hard-coded third-party plan multipliers from prerequisites text.

The exact usage multipliers and plan wording here are likely to drift quickly and become stale. Prefer linking to the pricing page without embedding detailed numeric claims.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 49 - 51, Remove the hard-coded numeric plan
multipliers and detailed pricing claims under the "Claude Subscription" section
and replace that text with a brief, generic recommendation directing users to
the official pricing page; update the "Claude Subscription" paragraph (and
mention of "Claude Code" install note if needed) to link to
https://claude.com/pricing and advise choosing a plan based on expected usage
(solo, team, enterprise) without asserting specific multipliers or exact plan
names.
.github/workflows/release.yml (1)

159-166: Consider centralizing git-crypt bootstrap across jobs.

The same install+unlock sequence is repeated in multiple jobs; extracting this into a reusable composite action (or YAML anchor) will reduce drift and make security updates easier.

Also applies to: 191-198

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 159 - 166, The install+unlock
steps for git-crypt are duplicated (steps named "Install git-crypt" and "Unlock
repository" that run apt-get and bash
.github/workflows/scripts/setup-git-crypt.sh); extract these into a single
reusable unit (either a composite action under .github/actions or a YAML
anchor/extension) and replace the repeated blocks in each job (including the
blocks at the current location and lines ~191-198) with a call to that composite
action or anchor to centralize installation and unlocking logic, pass
GIT_CRYPT_KEY as an input/env to the composite action, and update callers to use
the new action name (e.g., "git-crypt-bootstrap") so future updates occur in one
place.
docs/site/src/pages/docs/TeamsSection.tsx (1)

6-7: Include “Update” in the feature summary to reflect full CRUD actions.

Line 7 currently skips update, while this PR introduces Install/Update/Remove actions.

Suggested copy tweak
-    desc: "Browse, push, install, and remove assets from a visual Teams page",
+    desc: "Browse, push, install, update, and remove assets from a visual Teams page",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/site/src/pages/docs/TeamsSection.tsx` around lines 6 - 7, The feature
summary string (desc) in TeamsSection.tsx omits "Update" even though the PR adds
Install/Update/Remove actions; update the desc value (the "desc" property) to
include "Update" so it reads something like "Browse, push, install, update, and
remove assets from a visual Teams page" to accurately reflect CRUD actions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release-dev.yml:
- Line 41: The workflow currently excludes the console directory from Trivy
scans by listing "console" in the skip-dirs setting (skip-dirs:
".venv,node_modules,console/node_modules,launcher,docs/site/api,console"), which
contradicts the git-crypt decryption and the CI security-scan intent; either
remove "console" from the skip-dirs value in the release-dev.yml so Trivy scans
the decrypted console content, or keep the exclusion but add a clear
justification in .trivyignore (referencing the security-scan job and git-crypt
decryption) explaining why console is an accepted exclusion (e.g., documented
false positives or accepted risk).

In @.github/workflows/release.yml:
- Line 91: The workflow currently excludes the console directory via the
skip-dirs setting (skip-dirs:
'.venv,node_modules,console/node_modules,launcher,docs/site/api,console'),
causing Trivy to skip scanning decrypted console sources; update the skip-dirs
value to remove the standalone "console" entry (keep console/node_modules if
desired) so Trivy will scan the console code after it is decrypted (ensure the
existing console decryption step runs before Trivy), and verify .trivyignore
still documents the intended behavior.

In @.gitignore:
- Around line 231-232: The .gitignore entry '.env**' is too broad and hides
committed env templates; replace it with explicit ignore patterns (e.g., '.env',
'.env.local', '.env.*.local' or other runtime/private files you intend to
exclude) and add explicit allowlist entries for committed templates such as
'.env.example' and '.env.template' so those files remain tracked; update the
'.env**' entry in the .gitignore to the narrower patterns and add allowlist
lines for the example/template filenames.

In `@docs/site/src/components/InstallSection.tsx`:
- Around line 101-103: Fix the comma splice and missing object in the JSX
sentence inside InstallSection (the fragment containing "extracts knowledge, the
<strong>Teams dashboard</strong> shares with your team."): replace it with a
clear phrasing such as "extracts knowledge that the Teams dashboard shares with
your team." so the sentence reads smoothly and specifies what is being shared;
update the string in the InstallSection.tsx JSX where that fragment appears.

In `@docs/site/src/content/blog/team-vault-sharing-ai-assets.md`:
- Line 4: Frontmatter "description" contains an invalid YAML escape sequence
`developer\'s`; update the description value (the "description" frontmatter
field in team-vault-sharing-ai-assets.md) to remove the backslash so it reads
developer's (or alternatively switch to a single-quoted YAML string and escape
as needed), ensuring the double-quoted string contains a plain apostrophe
without `\`.

In `@docs/site/src/pages/docs/PrerequisitesSection.tsx`:
- Around line 60-83: The inline <code> element in PrerequisitesSection.tsx can
overflow on narrow screens; update the <code className="..."> for the npm
command token (the code element inside Claude Code paragraph) to enable wrapping
and prevent clipping by adding Tailwind utilities such as break-words (or
break-all) and whitespace-normal/whitespace-pre-wrap and/or display inline-block
(e.g., add "break-words whitespace-normal inline-block" or "break-all
whitespace-normal") so the long token wraps instead of causing horizontal
overflow.

In `@docs/site/src/pages/docs/TeamsSection.tsx`:
- Around line 64-65: Header copy in TeamsSection.tsx is inconsistent with the
listed shareable asset types; update the header string that currently reads
"Share rules, commands, and skills across your team via the Console dashboard"
to include "agents" (e.g., "Share agents, rules, commands, and skills across
your team via the Console dashboard") so the header matches the documented items
and locate the exact text in the TeamsSection component to replace it.

In `@installer/steps/dependencies.py`:
- Around line 413-415: The loop that disables clients inside install_sx
currently ignores _run_bash_with_retry results; modify install_sx so each call
to _run_bash_with_retry(f"sx clients disable {client}") checks the return value
and on failure logs a clear error referencing the client (e.g., "failed to
disable {client}") and returns False (or raises) so install_sx does not return
True on partial failure; use the existing logger (or raise RuntimeError) and
stop further processing when a disable fails to ensure proper user feedback and
error handling.

In `@README.md`:
- Around line 195-208: Update the "Guided onboarding" bullet under the "What
Teams includes" details: change the noun form "setup" to the verb phrase "set
up" so the sentence reads "New team members get a step-by-step screen to set
up"; locate the bullet that starts with "Guided onboarding — New team members
get a step-by-step screen to setup" and replace "setup" with "set up".

---

Nitpick comments:
In @.coderabbit.yaml:
- Around line 23-26: Update the inaccurate comment above the path_filters entry:
change or split the comment that currently says "Exclude git-crypt encrypted
directories from reviews (see .gitattributes)" so it no longer implies
console/** is encrypted; either revise the single comment to reflect the actual
intent (e.g., specify launcher/** is git-crypt encrypted and console/** is the
active Teams UI excluded for another reason) or add two separate comments
immediately above the respective filters referencing "!launcher/**" and
"!console/**" to clarify each exclusion's purpose.

In @.github/workflows/release.yml:
- Around line 159-166: The install+unlock steps for git-crypt are duplicated
(steps named "Install git-crypt" and "Unlock repository" that run apt-get and
bash .github/workflows/scripts/setup-git-crypt.sh); extract these into a single
reusable unit (either a composite action under .github/actions or a YAML
anchor/extension) and replace the repeated blocks in each job (including the
blocks at the current location and lines ~191-198) with a call to that composite
action or anchor to centralize installation and unlocking logic, pass
GIT_CRYPT_KEY as an input/env to the composite action, and update callers to use
the new action name (e.g., "git-crypt-bootstrap") so future updates occur in one
place.

In `@docs/site/src/components/WorkflowSteps.tsx`:
- Around line 195-204: Teams is rendered as a command in WorkflowSteps.tsx but
is not a slash command and also uses a <span> for the label while other items
use <code>, causing semantic and visual inconsistency; update the component by
either moving this block out of the "All Commands" group into a more generic
"Features" or "Tools" section (or rename the section) or keep it but make its
presentation consistent by replacing the <span className="text-sm font-medium
text-primary">Teams</span> with the same <code> element used by other entries so
visuals match; ensure you update any section title text (e.g., "All Commands")
and surrounding grouping logic in WorkflowSteps to reflect the chosen approach.

In `@docs/site/src/pages/docs/TeamsSection.tsx`:
- Around line 6-7: The feature summary string (desc) in TeamsSection.tsx omits
"Update" even though the PR adds Install/Update/Remove actions; update the desc
value (the "desc" property) to include "Update" so it reads something like
"Browse, push, install, update, and remove assets from a visual Teams page" to
accurately reflect CRUD actions.

In `@installer/ui.py`:
- Around line 163-165: The code redundantly uses tier_map.get(tier,
tier.title()) even though the preceding conditional guarantees tier in ("solo",
"team"); change the lookup to a direct dictionary index (e.g., tier_map[tier])
when assigning tier_display and remove the unreachable fallback expression so
tier_map and tier_display use a simple deterministic lookup for the tier
variable.

In `@README.md`:
- Around line 49-51: Remove the hard-coded numeric plan multipliers and detailed
pricing claims under the "Claude Subscription" section and replace that text
with a brief, generic recommendation directing users to the official pricing
page; update the "Claude Subscription" paragraph (and mention of "Claude Code"
install note if needed) to link to https://claude.com/pricing and advise
choosing a plan based on expected usage (solo, team, enterprise) without
asserting specific multipliers or exact plan names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 976dc08b-abfa-4305-85db-19ed3f2b61fe

📥 Commits

Reviewing files that changed from the base of the PR and between 94cb42c and 0a2e3f7.

⛔ Files ignored due to path filters (42)
  • docs/img/dashboard.png is excluded by !**/*.png
  • docs/img/teams.png is excluded by !**/*.png
  • docs/site/public/console/dashboard.png is excluded by !**/*.png
  • docs/site/public/console/teams.png is excluded by !**/*.png
  • docs/site/public/console/vault.png is excluded by !**/*.png
  • launcher/auth.py is excluded by !launcher/**
  • launcher/banner.py is excluded by !launcher/**
  • launcher/build.py is excluded by !launcher/**
  • launcher/claude_installer.py is excluded by !launcher/**
  • launcher/cli.py is excluded by !launcher/**
  • launcher/model_config.py is excluded by !launcher/**
  • launcher/statusline/formatter.py is excluded by !launcher/**
  • launcher/statusline/widgets.py is excluded by !launcher/**
  • launcher/tests/unit/statusline/test_formatter.py is excluded by !launcher/**
  • launcher/tests/unit/statusline/test_widgets.py is excluded by !launcher/**
  • launcher/tests/unit/test_auth.py is excluded by !launcher/**
  • launcher/tests/unit/test_claude_installer.py is excluded by !launcher/**
  • launcher/tests/unit/test_model_config.py is excluded by !launcher/**
  • launcher/tests/unit/test_wrapper.py is excluded by !launcher/**
  • launcher/trial.py is excluded by !launcher/**
  • launcher/utils.py is excluded by !launcher/**
  • launcher/wrapper.py is excluded by !launcher/**
  • pilot/agents/spec-reviewer.md is excluded by !pilot/**
  • pilot/commands/learn.md is excluded by !pilot/**
  • pilot/commands/spec-bugfix-verify.md is excluded by !pilot/**
  • pilot/commands/spec-plan.md is excluded by !pilot/**
  • pilot/commands/spec-verify.md is excluded by !pilot/**
  • pilot/commands/sync.md is excluded by !pilot/**
  • pilot/commands/vault.md is excluded by !pilot/**
  • pilot/hooks/_util.py is excluded by !pilot/**
  • pilot/hooks/tests/test__util.py is excluded by !pilot/**
  • pilot/rules/development-practices.md is excluded by !pilot/**
  • pilot/rules/standards-frontend.md is excluded by !pilot/**
  • pilot/rules/standards-golang.md is excluded by !pilot/**
  • pilot/rules/standards-python.md is excluded by !pilot/**
  • pilot/rules/standards-typescript.md is excluded by !pilot/**
  • pilot/rules/team-vault.md is excluded by !pilot/**
  • pilot/scripts/mcp-server.cjs is excluded by !pilot/**
  • pilot/scripts/worker-service.cjs is excluded by !pilot/**
  • pilot/settings.json is excluded by !pilot/**
  • pilot/ui/viewer-bundle.js is excluded by !pilot/**
  • pilot/ui/viewer.css is excluded by !pilot/**
📒 Files selected for processing (75)
  • .coderabbit.yaml
  • .gitattributes
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/ISSUE_TEMPLATE/feature_request.md
  • .github/workflows/claude.yml
  • .github/workflows/release-dev.yml
  • .github/workflows/release.yml
  • .gitignore
  • .trivyignore
  • LICENSE
  • README.md
  • console/src/services/worker-service.ts
  • console/src/services/worker/BranchManager.ts
  • console/src/services/worker/http/routes/SettingsRoutes.ts
  • console/src/services/worker/http/routes/TeamsRoutes.ts
  • console/src/services/worker/http/routes/TeamsTypes.ts
  • console/src/services/worker/http/routes/WorktreeRoutes.ts
  • console/src/ui/viewer/App.tsx
  • console/src/ui/viewer/components/CommandPalette.tsx
  • console/src/ui/viewer/components/LicenseBadge.tsx
  • console/src/ui/viewer/components/TeamGate.tsx
  • console/src/ui/viewer/constants/shortcuts.ts
  • console/src/ui/viewer/hooks/useSettings.ts
  • console/src/ui/viewer/hooks/useStats.ts
  • console/src/ui/viewer/hooks/useTeams.ts
  • console/src/ui/viewer/layouts/Sidebar/SidebarNav.tsx
  • console/src/ui/viewer/views/Dashboard/TeamsStatus.tsx
  • console/src/ui/viewer/views/Dashboard/VexorStatus.tsx
  • console/src/ui/viewer/views/Dashboard/index.tsx
  • console/src/ui/viewer/views/Settings/index.tsx
  • console/src/ui/viewer/views/Teams/TeamsAssetDetail.tsx
  • console/src/ui/viewer/views/Teams/TeamsAssetTable.tsx
  • console/src/ui/viewer/views/Teams/TeamsContentModal.tsx
  • console/src/ui/viewer/views/Teams/TeamsHelpModal.tsx
  • console/src/ui/viewer/views/Teams/TeamsOnboarding.tsx
  • console/src/ui/viewer/views/Teams/TeamsPushPanel.tsx
  • console/src/ui/viewer/views/Teams/TeamsSetupTab.tsx
  • console/src/ui/viewer/views/Teams/TeamsSummaryCards.tsx
  • console/src/ui/viewer/views/Teams/index.tsx
  • console/src/ui/viewer/views/index.ts
  • console/tests/hooks/use-teams.test.ts
  • console/tests/hooks/useSettings.test.ts
  • console/tests/settings-routes.test.ts
  • console/tests/ui/SidebarNav.test.ts
  • console/tests/ui/search-removal.test.ts
  • console/tests/ui/teams-install.test.ts
  • console/tests/ui/teams-view.test.ts
  • console/tests/ui/vexor-status-widget.test.ts
  • console/tests/ui/views-index.test.ts
  • console/tests/worker/teams-routes.test.ts
  • docs/site/src/components/ConsoleSection.tsx
  • docs/site/src/components/DeepDiveSection.tsx
  • docs/site/src/components/FAQSection.tsx
  • docs/site/src/components/HeroSection.tsx
  • docs/site/src/components/InstallSection.tsx
  • docs/site/src/components/PricingSection.tsx
  • docs/site/src/components/TeamsDashboardSection.tsx
  • docs/site/src/components/WhatsInside.tsx
  • docs/site/src/components/WorkflowSteps.tsx
  • docs/site/src/content/blog/claude-code-settings-reference.md
  • docs/site/src/content/blog/slash-commands-and-init.md
  • docs/site/src/content/blog/team-vault-sharing-ai-assets.md
  • docs/site/src/pages/DocsPage.tsx
  • docs/site/src/pages/Index.tsx
  • docs/site/src/pages/docs/ConsoleSection.tsx
  • docs/site/src/pages/docs/InstallationSection.tsx
  • docs/site/src/pages/docs/LearnSection.tsx
  • docs/site/src/pages/docs/PrerequisitesSection.tsx
  • docs/site/src/pages/docs/RulesSection.tsx
  • docs/site/src/pages/docs/SyncSection.tsx
  • docs/site/src/pages/docs/TeamsSection.tsx
  • installer/cli.py
  • installer/steps/dependencies.py
  • installer/tests/unit/steps/test_dependencies.py
  • installer/ui.py
💤 Files with no reviewable changes (4)
  • .github/ISSUE_TEMPLATE/config.yml
  • installer/cli.py
  • docs/site/src/content/blog/slash-commands-and-init.md
  • installer/tests/unit/steps/test_dependencies.py
✅ Files skipped from review due to trivial changes (8)
  • docs/site/src/components/DeepDiveSection.tsx
  • docs/site/src/pages/docs/RulesSection.tsx
  • docs/site/src/pages/docs/SyncSection.tsx
  • .gitattributes
  • docs/site/src/components/HeroSection.tsx
  • docs/site/src/pages/docs/InstallationSection.tsx
  • .trivyignore
  • docs/site/src/components/PricingSection.tsx

exit-code: "1"
ignore-unfixed: true
skip-dirs: ".venv,node_modules,console/node_modules,launcher,docs/site/api"
skip-dirs: ".venv,node_modules,console/node_modules,launcher,docs/site/api,console"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, verify the workflow file exists and check lines around 24-30 and 41
cat -n .github/workflows/release-dev.yml | head -50

Repository: maxritter/pilot-shell

Length of output: 1735


🏁 Script executed:

# Check if .trivyignore exists and what it contains
ls -la | grep trivyignore
cat .trivyignore 2>/dev/null || echo "No .trivyignore found"

Repository: maxritter/pilot-shell

Length of output: 862


🏁 Script executed:

# Check the structure of the console directory
fd -t d console . --max-depth 1 2>/dev/null

Repository: maxritter/pilot-shell

Length of output: 75


🏁 Script executed:

# Check for pre-commit configuration
cat .pre-commit-config.yaml 2>/dev/null || echo "No .pre-commit-config.yaml found"

Repository: maxritter/pilot-shell

Length of output: 97


🏁 Script executed:

# Check git history for when console was added to skip-dirs (recent commits)
git log --oneline -20 -- .github/workflows/release-dev.yml 2>/dev/null | head -10

Repository: maxritter/pilot-shell

Length of output: 168


Confirm concern: console in skip-dirs contradicts the intent to scan decrypted content.

The workflow decrypts the repository via git-crypt (lines 24-30), but then Trivy skips scanning console entirely on line 41. The .trivyignore file documents that "In CI, the security-scan job decrypts them before scanning," indicating the intent is to scan decrypted directories. However, adding console to skip-dirs defeats this purpose.

Either remove console from skip-dirs to scan the decrypted content, or document in .trivyignore the specific reason for the exclusion (e.g., known false positives, acceptable risk, or design decision).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release-dev.yml at line 41, The workflow currently
excludes the console directory from Trivy scans by listing "console" in the
skip-dirs setting (skip-dirs:
".venv,node_modules,console/node_modules,launcher,docs/site/api,console"), which
contradicts the git-crypt decryption and the CI security-scan intent; either
remove "console" from the skip-dirs value in the release-dev.yml so Trivy scans
the decrypted console content, or keep the exclusion but add a clear
justification in .trivyignore (referencing the security-scan job and git-crypt
decryption) explaining why console is an accepted exclusion (e.g., documented
false positives or accepted risk).

exit-code: '1'
ignore-unfixed: true
skip-dirs: '.venv,node_modules,console/node_modules,launcher,docs/site/api'
skip-dirs: '.venv,node_modules,console/node_modules,launcher,docs/site/api,console'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Checking Trivy skip-dirs in workflow ==="
rg -n "skip-dirs:" .github/workflows/release.yml -C2

echo
echo "=== Checking .trivyignore for encrypted-dir expectations ==="
if [ -f ".trivyignore" ]; then
  rg -n "git-crypt|encrypted|decrypts" .trivyignore -C1 || echo "No matching patterns found"
else
  echo ".trivyignore file not found"
fi

echo
echo "=== Full Trivy configuration section in workflow ==="
rg -n "trivy" .github/workflows/release.yml -A5 -B5

Repository: maxritter/pilot-shell

Length of output: 1993


Remove console from Trivy skip-dirs to prevent security scan blind spot.

The console directory is git-crypt encrypted and explicitly decrypted in this workflow before scanning (as documented in .trivyignore), yet it's included in skip-dirs on line 91, preventing any vulnerability or secret scanning of decrypted console sources. This contradicts the documented intent and creates a security coverage gap in a high-change area.

🔧 Proposed fix
-          skip-dirs: '.venv,node_modules,console/node_modules,launcher,docs/site/api,console'
+          skip-dirs: '.venv,node_modules,console/node_modules,launcher,docs/site/api'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
skip-dirs: '.venv,node_modules,console/node_modules,launcher,docs/site/api,console'
skip-dirs: '.venv,node_modules,console/node_modules,launcher,docs/site/api'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml at line 91, The workflow currently excludes
the console directory via the skip-dirs setting (skip-dirs:
'.venv,node_modules,console/node_modules,launcher,docs/site/api,console'),
causing Trivy to skip scanning decrypted console sources; update the skip-dirs
value to remove the standalone "console" entry (keep console/node_modules if
desired) so Trivy will scan the console code after it is decrypted (ensure the
existing console decryption step runs before Trivy), and verify .trivyignore
still documents the intended behavior.

Comment on lines +231 to 232
sx.json
.env**
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

.env** is overly broad and may hide tracked env templates.

Ignoring .env** will also ignore files like .env.example / .env.template, which are often intentionally committed. Prefer a narrower pattern and explicit allowlist entries.

Suggested `.gitignore` tweak
-sx.json
-.env**
+sx.json
+.env
+.env.*
+!.env.example
+!.env.template
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 231 - 232, The .gitignore entry '.env**' is too
broad and hides committed env templates; replace it with explicit ignore
patterns (e.g., '.env', '.env.local', '.env.*.local' or other runtime/private
files you intend to exclude) and add explicit allowlist entries for committed
templates such as '.env.example' and '.env.template' so those files remain
tracked; update the '.env**' entry in the .gitignore to the narrower patterns
and add allowlist lines for the example/template filenames.

Comment on lines +101 to 103
extracts knowledge, the{" "}
<strong>Teams dashboard</strong>{" "}
shares with your team.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix grammar/clarity in the post-install guidance sentence.

At Line 101, the sentence uses a comma splice and leaves “shares” without a clear object, which makes the instruction read awkwardly.

✏️ Proposed copy fix
-                  extracts knowledge, the{" "}
-                  <strong>Teams dashboard</strong>{" "}
-                  shares with your team.
+                  extracts knowledge, and the{" "}
+                  <strong>Teams dashboard</strong>{" "}
+                  helps you share with your team.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extracts knowledge, the{" "}
<strong>Teams dashboard</strong>{" "}
shares with your team.
extracts knowledge, and the{" "}
<strong>Teams dashboard</strong>{" "}
helps you share with your team.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/site/src/components/InstallSection.tsx` around lines 101 - 103, Fix the
comma splice and missing object in the JSX sentence inside InstallSection (the
fragment containing "extracts knowledge, the <strong>Teams dashboard</strong>
shares with your team."): replace it with a clear phrasing such as "extracts
knowledge that the Teams dashboard shares with your team." so the sentence reads
smoothly and specifies what is being shared; update the string in the
InstallSection.tsx JSX where that fragment appears.

slug: "team-vault-sharing-ai-assets"
title: "Sharing AI Assets Across Your Team with Vault"
description: "Sync rules, commands, skills, and hooks across your team. Keep every developer\'s Claude consistent with a shared vault."
description: "Sync rules, skills, commands, and agents across your team. Keep every developer\'s Claude consistent with a shared repository."
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and examine the frontmatter
head -20 docs/site/src/content/blog/team-vault-sharing-ai-assets.md

Repository: maxritter/pilot-shell

Length of output: 908


🏁 Script executed:

# Run the verification script from the review comment
rg -n "\\\\'" docs/site/src/content/blog/team-vault-sharing-ai-assets.md

Repository: maxritter/pilot-shell

Length of output: 208


🏁 Script executed:

# Also check the exact state of line 4
sed -n '4p' docs/site/src/content/blog/team-vault-sharing-ai-assets.md | cat -A

Repository: maxritter/pilot-shell

Length of output: 207


Fix invalid YAML escape sequence in frontmatter description.

Line 4 contains developer\'s in a double-quoted YAML string. Single quotes do not require escaping in YAML double-quoted strings; \' is not a valid escape sequence and may cause parsing issues or unexpected behavior depending on YAML parser strictness.

Proposed fix
-description: "Sync rules, skills, commands, and agents across your team. Keep every developer\'s Claude consistent with a shared repository."
+description: "Sync rules, skills, commands, and agents across your team. Keep every developer's Claude consistent with a shared repository."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: "Sync rules, skills, commands, and agents across your team. Keep every developer\'s Claude consistent with a shared repository."
description: "Sync rules, skills, commands, and agents across your team. Keep every developer's Claude consistent with a shared repository."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/site/src/content/blog/team-vault-sharing-ai-assets.md` at line 4,
Frontmatter "description" contains an invalid YAML escape sequence
`developer\'s`; update the description value (the "description" frontmatter
field in team-vault-sharing-ai-assets.md) to remove the backslash so it reads
developer's (or alternatively switch to a single-quoted YAML string and escape
as needed), ensuring the double-quoted string contains a plain apostrophe
without `\`.

Comment on lines +60 to +83
<div className="rounded-xl p-4 border border-primary/20 bg-primary/5 mb-6">
<div className="flex items-center gap-2 mb-2">
<Terminal className="h-4 w-4 text-primary" />
<h3 className="font-semibold text-foreground text-sm">
Claude Code (Required)
</h3>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">
Pilot Shell installs <span className="text-foreground font-medium">on top of Claude Code</span> — you must install it first.
Install via npm:{" "}
<code className="text-primary bg-primary/10 px-1 py-0.5 rounded">npm install -g @anthropic-ai/claude-code</code>
{" "}or use the{" "}
<a
href="https://docs.anthropic.com/en/docs/claude-code/setup"
className="text-primary hover:underline"
target="_blank"
rel="noopener noreferrer"
>
native installer
</a>
. Pilot Shell does not install or update Claude Code for you.
</p>
</div>

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prevent inline command overflow on small screens.

The npm command token can overflow in narrow viewports. Add wrapping behavior on the <code> element to avoid clipping/horizontal scroll.

💡 Suggested tweak
-            <code className="text-primary bg-primary/10 px-1 py-0.5 rounded">npm install -g `@anthropic-ai/claude-code`</code>
+            <code className="text-primary bg-primary/10 px-1 py-0.5 rounded break-all">npm install -g `@anthropic-ai/claude-code`</code>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="rounded-xl p-4 border border-primary/20 bg-primary/5 mb-6">
<div className="flex items-center gap-2 mb-2">
<Terminal className="h-4 w-4 text-primary" />
<h3 className="font-semibold text-foreground text-sm">
Claude Code (Required)
</h3>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">
Pilot Shell installs <span className="text-foreground font-medium">on top of Claude Code</span> you must install it first.
Install via npm:{" "}
<code className="text-primary bg-primary/10 px-1 py-0.5 rounded">npm install -g @anthropic-ai/claude-code</code>
{" "}or use the{" "}
<a
href="https://docs.anthropic.com/en/docs/claude-code/setup"
className="text-primary hover:underline"
target="_blank"
rel="noopener noreferrer"
>
native installer
</a>
. Pilot Shell does not install or update Claude Code for you.
</p>
</div>
<div className="rounded-xl p-4 border border-primary/20 bg-primary/5 mb-6">
<div className="flex items-center gap-2 mb-2">
<Terminal className="h-4 w-4 text-primary" />
<h3 className="font-semibold text-foreground text-sm">
Claude Code (Required)
</h3>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">
Pilot Shell installs <span className="text-foreground font-medium">on top of Claude Code</span> you must install it first.
Install via npm:{" "}
<code className="text-primary bg-primary/10 px-1 py-0.5 rounded break-all">npm install -g `@anthropic-ai/claude-code`</code>
{" "}or use the{" "}
<a
href="https://docs.anthropic.com/en/docs/claude-code/setup"
className="text-primary hover:underline"
target="_blank"
rel="noopener noreferrer"
>
native installer
</a>
. Pilot Shell does not install or update Claude Code for you.
</p>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/site/src/pages/docs/PrerequisitesSection.tsx` around lines 60 - 83, The
inline <code> element in PrerequisitesSection.tsx can overflow on narrow
screens; update the <code className="..."> for the npm command token (the code
element inside Claude Code paragraph) to enable wrapping and prevent clipping by
adding Tailwind utilities such as break-words (or break-all) and
whitespace-normal/whitespace-pre-wrap and/or display inline-block (e.g., add
"break-words whitespace-normal inline-block" or "break-all whitespace-normal")
so the long token wraps instead of causing horizontal overflow.

Comment on lines +64 to +65
Share rules, commands, and skills across your team via the Console
dashboard
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Header copy is inconsistent with listed shareable asset types.

Line 64 mentions rules/commands/skills, but this section also documents agents as shareable.

Suggested copy tweak
-              Share rules, commands, and skills across your team via the Console
+              Share rules, commands, skills, and agents across your team via the Console
               dashboard
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Share rules, commands, and skills across your team via the Console
dashboard
Share rules, commands, skills, and agents across your team via the Console
dashboard
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/site/src/pages/docs/TeamsSection.tsx` around lines 64 - 65, Header copy
in TeamsSection.tsx is inconsistent with the listed shareable asset types;
update the header string that currently reads "Share rules, commands, and skills
across your team via the Console dashboard" to include "agents" (e.g., "Share
agents, rules, commands, and skills across your team via the Console dashboard")
so the header matches the documented items and locate the exact text in the
TeamsSection component to replace it.

Comment on lines +413 to +415
# Disable all clients except claude-code to prevent .cursor/.gemini folders
for client in ("gemini", "cursor", "github-copilot", "codex"):
_run_bash_with_retry(f"sx clients disable {client}")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n installer/steps/dependencies.py | sed -n '400,430p'

Repository: maxritter/pilot-shell

Length of output: 1252


🏁 Script executed:

# Find the install_sx function definition to understand full context
rg -n "def install_sx" installer/steps/dependencies.py -A 30

Repository: maxritter/pilot-shell

Length of output: 1230


🏁 Script executed:

# Search for _run_bash_with_retry definition to understand its behavior
rg -n "_run_bash_with_retry" installer/steps/dependencies.py -B 2 -A 8 | head -50

Repository: maxritter/pilot-shell

Length of output: 1865


🌐 Web query:

sleuth-io sx CLI clients disable command documentation

💡 Result:

SX CLI has a clients command group for managing which AI coding assistants receive installed assets:

  • Disable a client
    • Command: sx clients disable <client-id>
    • What it does: disables that client “so that assets will not be installed to it.” [1]
  • Valid <client-id> values: claude-code, cursor, gemini, github-copilot, codex [2]

Related commands:

  • sx clients — list detected clients and their installation status (supports --json). [1]
  • sx clients enable <client-id> — re-enable a client. [1]
  • sx clients reset — revert to default behavior (assets install to all detected clients). [1]

Sources: [1] [2]


Handle sx clients disable failures explicitly.

At lines 414-415, the disable calls ignore their return values; if any call fails, install_sx() still returns True, so users get no feedback and the post-install state may be incomplete.

Suggested fix
    # Disable all clients except claude-code to prevent .cursor/.gemini folders
-    for client in ("gemini", "cursor", "github-copilot", "codex"):
-        _run_bash_with_retry(f"sx clients disable {client}")
+    failed_clients: list[str] = []
+    for client in ("gemini", "cursor", "github-copilot", "codex"):
+        if not _run_bash_with_retry(f"sx clients disable {client}"):
+            failed_clients.append(client)
+
+    if failed_clients:
+        return False

Per coding guidelines, installer/**/*.py must include proper error handling and user feedback.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Disable all clients except claude-code to prevent .cursor/.gemini folders
for client in ("gemini", "cursor", "github-copilot", "codex"):
_run_bash_with_retry(f"sx clients disable {client}")
# Disable all clients except claude-code to prevent .cursor/.gemini folders
failed_clients: list[str] = []
for client in ("gemini", "cursor", "github-copilot", "codex"):
if not _run_bash_with_retry(f"sx clients disable {client}"):
failed_clients.append(client)
if failed_clients:
return False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@installer/steps/dependencies.py` around lines 413 - 415, The loop that
disables clients inside install_sx currently ignores _run_bash_with_retry
results; modify install_sx so each call to _run_bash_with_retry(f"sx clients
disable {client}") checks the return value and on failure logs a clear error
referencing the client (e.g., "failed to disable {client}") and returns False
(or raises) so install_sx does not return True on partial failure; use the
existing logger (or raise RuntimeError) and stop further processing when a
disable fails to ensure proper user feedback and error handling.

Comment on lines +195 to +208
### Team Asset Sharing

Share rules, skills, commands, and agents across your team from a central, private Git repository:

<img src="docs/img/teams.png" alt="Teams Dashboard — shared asset management" width="700">

<details>
<summary><b>What Teams includes</b></summary>

- **Shared asset management** — Push and install rules, skills, commands, and agents from the Console UI
- **Project-scoped** — Assets are tied to repositories, so each project gets exactly what it needs
- **Automatic versioning** — See installed vs. latest versions at a glance
- **Guided onboarding** — New team members get a step-by-step screen to setup

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix grammar in onboarding bullet (setupset up).

Line 207 should use the verb form: “step-by-step screen to set up”.

✏️ Proposed doc fix
-- **Guided onboarding** — New team members get a step-by-step screen to setup
+- **Guided onboarding** — New team members get a step-by-step screen to set up
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 195 - 208, Update the "Guided onboarding" bullet
under the "What Teams includes" details: change the noun form "setup" to the
verb phrase "set up" so the sentence reads "New team members get a step-by-step
screen to set up"; locate the bullet that starts with "Guided onboarding — New
team members get a step-by-step screen to setup" and replace "setup" with "set
up".

@maxritter maxritter merged commit e8f59fc into main Mar 5, 2026
10 of 12 checks passed
github-actions bot pushed a commit that referenced this pull request Mar 5, 2026
# [7.2.0](v7.1.5...v7.2.0) (2026-03-05)

### Features

* New Teams asset sharing dashboard for skills, rules, commands and agents ([#90](#90)) ([e8f59fc](e8f59fc))
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

🎉 This PR is included in version 7.2.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant