Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/designs/data-directory-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,14 @@ every checkout, so that is where they live now:
├── learnings-wt/ getWorktreeDir → <dataHome>/<dirname>
├── reports-wt/ (the side-branch locks sit beside them)
├── pending-learnings/ pendingLearningsDir → <dataHome>/pending-learnings
├── managed-main-checkout-hooks.json team hooks teamai wrote ungated into the main checkout's Claude Code / Codex
│ settings, shared by every checkout (#955; the built-in hooks stay in HOME)
│ project toolPaths choose the files; Claude uses settings.local.json beside its settings file
│ Codex records event, matcher-group position and complete rendered entry; unique definitions recover moved entries
│ legacy ownership matches event/matcher/command uniquely, ignoring unrecorded timeout/context options
│ pre-#370 Codex ownership is imported from <main>/.teamai/managed-hooks.json before reconcile/removal
└── workspaces/<managedMcpWorkspaceId(root)>/
├── managed-main-checkout-hooks.json bare repositories only: this workspace owns its Claude / Codex team-hook files and trust target
├── managed-mcp.json managedMcpManifestPath, one per checkout; Copilot placement is true for bare, false for keyed, absent when unproven
├── managed-mcp-files.json resolvedMcpFilesPath: project MCP configs teamai may have written a resolved ${VAR} to, and whether
│ the paths earlier teamai.yaml revisions mapped were read; one of those git tracks is marked tracked (#882);
Expand Down
19 changes: 11 additions & 8 deletions docs/usage-guide.md

Large diffs are not rendered by default.

19 changes: 11 additions & 8 deletions docs/usage-guide.zh-CN.md

Large diffs are not rendered by default.

34 changes: 26 additions & 8 deletions skill-data/core/references/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ This is the #1 onboarding issue. In order:
```bash
teamai hooks inject
```
4. **Wrong scope?** Project-scope hooks are written to your HOME tool settings
(e.g. `~/.claude/settings.json`), not the project folder — that is intentional.
4. **Wrong scope?** Project-scope built-in hooks are written to your HOME tool
settings (e.g. `~/.claude/settings.json`), not the project folder; the team's own
hooks for Claude Code and Codex go to the main checkout
(`.claude/settings.local.json`, `.codex/hooks.json`). That is intentional.
Removal also checks existing Claude/Codex main-checkout hook files when the
HOME tool root is missing or relocated.
In project scope, `teamai hooks remove` preserves other projects' gated team
hooks in HOME, while removing the shared built-in hooks.
If you initialized project scope but expected machine-wide resources, re-run
with `--scope user`.
5. **Tool has no hook surface** (e.g. Gemini CLI, JoyCode): there is no auto-sync;
Expand Down Expand Up @@ -142,7 +148,7 @@ broken machine):
| Tool | Hooks status | Why |
|-----------------------|---------------------------|---------------------------------------------------------------------|
| Claude Code (`claude`)| Installed | Fully supported — this is the main, working path |
| Codex | Written but **trust-gated** or skipped | Codex gates non-managed hooks behind an explicit trust step; `teamai doctor` prints a reminder to trust them |
| Codex | Installed and trusted | Codex runs only trusted hooks; teamai trusts the ones it writes through `codex app-server`, and `teamai doctor` names any Codex will not run |
| Cursor | Installed | Also runs `~/.claude/settings.json`. That copy exits only when `~/.cursor/hooks.json` or the project `.cursor/hooks.json` contains `--tool cursor` |
| Copilot CLI | Installed in self mode | Also runs a trusted project's `.claude/settings.json`. That copy exits only when `.github/hooks/teamai.json` contains `--tool copilot`. `COPILOT_CLI` alone does not skip |
| CodeBuddy / WorkBuddy | Installed | Claude-format hooks in their own `settings.json` |
Expand All @@ -161,11 +167,23 @@ step — do not assume auto-sync just works.

### Codex

Codex gates non-managed hooks behind an explicit **trust** step. `teamai init` /
`teamai hooks inject` may write the hooks, but Codex won't run them until the user
trusts them (`teamai doctor` prints a reminder when it detects this). Guide the
user to trust the teamai hooks in Codex, then reopen a session. Until then, run
`teamai pull` manually.
Codex runs a non-managed hook only once it is **trusted**. `teamai init`, `pull`
and `teamai hooks inject` trust the hooks they write (and, in a project, the main
checkout, or the current worktree for a bare repository) through `codex app-server`.
Trust written by a session-start pull applies from the next Codex session. `teamai doctor` names any teamai hook Codex will not
run. Then: run `teamai pull`; if `codex` is not on PATH or `codexTrustEnabled: false`
is set in `config.yaml`, guide the user to trust the teamai hooks in Codex `/hooks`,
then reopen a session. A new linked worktree gets the team hooks from its second
Codex session (the first creates its `.codex/`). Member hooks with the same command
are preserved and remain untouched by automatic trust. Codex ownership uses the
recorded event, position and complete entry. A moved entry is recovered only by a
unique full-definition match. Legacy records recover only a unique event, matcher
and command match; `timeout` and `additionalContextLimit` were not recorded.
Pre-#370 project Codex ownership is imported from the main checkout's
`.teamai/managed-hooks.json` before reconciliation or direct removal.
Unrecorded or ambiguous legacy team-hook copies are preserved. Project hook paths follow `toolPaths`;
Claude uses `settings.local.json` beside its configured settings file. A custom
Codex path that Codex does not load is reported as `not loaded` by doctor.

### Cursor

Expand Down
5 changes: 3 additions & 2 deletions skill-data/setup/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ and create-repo URLs, and the per-provider caveats, and points at
install. Let `teamai init` set up every AI tool already installed (omitting
`--agent` gives an interactive picker; select all detected tools). **After init,
report which agents were set up** — in the user's language, which tools now
auto-start TeamAI, and which detected tools were skipped and why (e.g. Codex
trust-gate, CodeBuddy design). Verify the real per-tool result with
auto-start TeamAI, which detected tools were skipped and why (e.g. CodeBuddy
design), and any installed hooks that still need trust (e.g. Codex with
automatic trust disabled or unavailable). Verify the real per-tool result with
`teamai doctor` and `teamai hooks list`.
3. **After init, resources appear on the NEXT session.** `teamai init` injects a
session-start hook that auto-runs `teamai pull`. Empty skills/rules directories
Expand Down
5 changes: 3 additions & 2 deletions skill-data/setup/references/setup-admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,9 @@ login` run in an interactive shell (see Step 3).
Claude Code"). Omitting `--agent` gives an interactive picker — select **every AI
tool already installed** on the machine. Then **report back which agents were set
up**, in the user's language: name the tools that will now auto-start TeamAI, and
any detected tool that was skipped and why (e.g. Codex trust-gate,
CodeBuddy/WorkBuddy by design — see the troubleshooting reference, `"$(teamai skill path core)/references/troubleshooting.md"`).
any detected tool that was skipped and why (e.g. CodeBuddy/WorkBuddy by design),
and any installed hooks that still need trust (e.g. Codex with automatic trust
disabled or unavailable). See the troubleshooting reference, `"$(teamai skill path core)/references/troubleshooting.md"`.

## Step 6 — Verify with doctor

Expand Down
1 change: 1 addition & 0 deletions src/__tests__/anchors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ describe('defaultProjectSlug (#809)', () => {
const checkout = path.join(base, 'bare-layout', wt);
git(bare, 'worktree', 'add', '-q', checkout);
expect((await resolveAnchors(checkout))?.projectAnchor).toBe(bare);
expect((await resolveAnchors(checkout))?.projectAnchorIsBare).toBe(true);
expect(await defaultProjectSlug(checkout)).toBe('bare-layout');
}
const sub = path.join(base, 'bare-layout', 'main', 'pkg');
Expand Down
39 changes: 38 additions & 1 deletion src/__tests__/bootstrap-self.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import YAML from 'yaml';
vi.mock('../providers/index.js', async (importOriginal) => ({
...await importOriginal<typeof import('../providers/index.js')>(),
getProvider: vi.fn(() => ({
isAuthenticated: () => true,
authenticate: async () => 'tester',
parseRepoInput: (remote: string) => ({ httpsUrl: remote }),
})),
}));

import { installFakeCodex, readFakeCodexState } from './helpers/fake-codex.js';
import { bootstrapSelfRepo } from '../bootstrap.js';
import { detectProjectConfig } from '../config.js';

Expand All @@ -13,6 +23,7 @@ beforeEach(() => {
});

afterEach(() => {
vi.unstubAllEnvs();
fs.rmSync(tmpDir, { recursive: true, force: true });
});

Expand All @@ -33,6 +44,32 @@ describe('bootstrapSelfRepo', () => {
expect(result).toBe('skip');
});

it('trusts self project hooks during a silent bootstrap', async () => {
tmpDir = fs.realpathSync.native(tmpDir);
const home = path.join(tmpDir, 'home');
const project = path.join(tmpDir, 'project');
const teamaiDir = path.join(project, '.teamai');
const codexHome = path.join(home, '.codex');
fs.mkdirSync(teamaiDir, { recursive: true });
fs.mkdirSync(codexHome, { recursive: true });
const fakeBin = installFakeCodex();
vi.stubEnv('HOME', home);
vi.stubEnv('PATH', `${fakeBin}${path.delimiter}${process.env.PATH ?? ''}`);
fs.writeFileSync(path.join(teamaiDir, 'teamai.yaml'), YAML.stringify({
team: 'test', mode: 'self', repo: 'https://github.com/acme/app.git', provider: 'github',
toolPaths: { codex: { skills: '.codex/skills', settings: '.codex/hooks.json' } },
}));
try {
expect(await bootstrapSelfRepo(project, { silent: true })).toBe('bootstrapped');
const state = readFakeCodexState(codexHome);
expect(state.projects[project]).toEqual({ trust_level: 'trusted' });
expect(Object.keys(state.hooksState).length).toBeGreaterThan(0);
expect(Object.keys(state.hooksState).every((key) => key.startsWith(path.join(project, '.codex', 'hooks.json')))).toBe(true);
} finally {
fs.rmSync(fakeBin, { recursive: true, force: true });
}
});

it("returns 'already' when a local config.yaml is already present", async () => {
const teamaiDir = path.join(tmpDir, '.teamai');
fs.mkdirSync(teamaiDir, { recursive: true });
Expand Down
Loading
Loading