Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e514d36
docs:知识库飞轮系统设计roadmap文档
Jun 5, 2026
7455087
feat: phase 1 - agents support, multi-index search, recall subagent &…
Jun 7, 2026
941553f
docs: update roadmap文档
m0Nst3r873 Jun 8, 2026
7e9c7de
feat(search): P1.4 domain inference + search weighting
m0Nst3r873 Jun 8, 2026
16f7ae0
test(validation): add Phase 1 E2E suite and acceptance report
m0Nst3r873 Jun 8, 2026
b2aef31
docs: update roadmap — add P4.6 learning promotion mechanism
m0Nst3r873 Jun 8, 2026
f7dfaad
Merge pull request #1 from m0Nst3r873/worktree-feature+p1.4-domain-in…
m0Nst3r873 Jun 8, 2026
4ef5a07
docs(validation): update phase1 report to reflect E2E bug fix
m0Nst3r873 Jun 8, 2026
b4e4db6
docs(validation): add runtime evidence appendix to phase1 report
m0Nst3r873 Jun 8, 2026
a8a6310
feat(search): query-aware domain weights + IDF scoring (v4 index)
m0Nst3r873 Jun 8, 2026
f95fe7c
feat(import): add teamai import command — Phase 0 cold-start + P4.4 M…
m0Nst3r873 Jun 9, 2026
fec7ced
Merge pull request #2 from m0Nst3r873/worktree-feature+phase0-p44-import
m0Nst3r873 Jun 9, 2026
e791d8b
fix(import): support claude-internal CLI + gh REST API fallback + rea…
m0Nst3r873 Jun 9, 2026
779c4cb
Merge pull request #3 from m0Nst3r873/worktree-feature+phase0-p44-import
m0Nst3r873 Jun 9, 2026
c9eb178
fix(codebase): require file-path prefix in module descriptions for ag…
m0Nst3r873 Jun 9, 2026
336b600
feat(codebase): upgrade workspace + MR prompts to A1-level documentat…
m0Nst3r873 Jun 9, 2026
d76f4a8
docs(validation): replace A1+A4 codebase docs with real teamai-cli ge…
m0Nst3r873 Jun 9, 2026
588ddf0
feat(mr-hint): add SessionStart hook to hint AI about unimported merg…
m0Nst3r873 Jun 10, 2026
d508520
feat(mr-hint): add GitHub REST API fallback when gh CLI unavailable
m0Nst3r873 Jun 10, 2026
2cbc278
docs(validation): update public acceptance report for P4.4 mr-hint tr…
m0Nst3r873 Jun 10, 2026
771a875
feat(ai-client): support login shell PATH + extend CLI candidates
m0Nst3r873 Jun 10, 2026
c1e5e13
docs(validation): update A4 with real before/after codebase demo
m0Nst3r873 Jun 10, 2026
bfba491
Merge pull request #4 from m0Nst3r873/worktree-feature+p4-4-mr-hint
m0Nst3r873 Jun 10, 2026
b4bbb11
fix(providers): add TGit REST API fallback + multi-shell CLI detection
m0Nst3r873 Jun 10, 2026
29da3ee
docs: 验收文档typo更正
m0Nst3r873 Jun 10, 2026
0f58dfc
fix(ai-client): multi-CLI compat + shell injection hardening
m0Nst3r873 Jun 10, 2026
a166ebd
feat(codebase): align with llm-wiki — frontmatter / index / lint / mu…
m0Nst3r873 Jun 10, 2026
a456f9a
Merge pull request #5 from m0Nst3r873/worktree-codebase-llm-wiki-opti…
m0Nst3r873 Jun 10, 2026
d2fca83
feat(search): codebase-index.md high-weight + skip codebase.md
m0Nst3r873 Jun 10, 2026
ff331c2
docs(validation): refresh A1/A4 with llm-wiki-optimized codebase output
m0Nst3r873 Jun 10, 2026
3161efb
docs(validation): replace codebase-after full content with before/aft…
m0Nst3r873 Jun 10, 2026
1777051
docs(roadmap): add Phase 6 — Phase 5 hardening
m0Nst3r873 Jun 11, 2026
888aa5e
feat(import): Phase 5 — team-level codebase aggregation
m0Nst3r873 Jun 11, 2026
8ce2d96
Merge pull request #6 from m0Nst3r873/feature/team-codebase-phase5
m0Nst3r873 Jun 11, 2026
4651a7c
feat(agents): multi-CLI subagent sync + security hardening
m0Nst3r873 Jun 11, 2026
28cacd8
Merge pull request #7 from m0Nst3r873/worktree-feature+multi-cli-suba…
m0Nst3r873 Jun 11, 2026
2580f1e
feat: Phase 6 — Phase 5 hardening pass
m0Nst3r873 Jun 11, 2026
8ab73b7
Merge pull request #8 from m0Nst3r873/feature/team-codebase-phase6
m0Nst3r873 Jun 11, 2026
6e435aa
chore: stop tracking local drafts (roadmap, validation, .codebuddy)
m0Nst3r873 Jun 11, 2026
044e86e
docs(readme): trim subagent phase notes and add Phase 5/6 commands
m0Nst3r873 Jun 11, 2026
bad5cdd
fix(p5-p6): address audit findings — 2 blockers, 1 major, 5 medium
m0Nst3r873 Jun 11, 2026
f533ebc
Merge pull request #9 from m0Nst3r873/worktree-fix-p5-p6-audit
m0Nst3r873 Jun 11, 2026
7a74f92
fix(test): add missing `type` field to recall.test.ts SearchIndexEntr…
m0Nst3r873 Jun 11, 2026
8325e88
fix(test): resolve cross-platform path assertion failures in review-c…
m0Nst3r873 Jun 12, 2026
68f5850
Merge remote-tracking branch 'upstream/main'
m0Nst3r873 Jun 15, 2026
aa869d9
feat(contribute-check): Phase 2 知识库空白感知 + git commit 降权
m0Nst3r873 Jun 15, 2026
c828c44
Merge pull request #10 from m0Nst3r873/worktree-phase2-contribute-check
m0Nst3r873 Jun 15, 2026
2fb0143
chore: MVP 发布前清理——移除 --limit 死选项,import 完成后提示 push
m0Nst3r873 Jun 15, 2026
2ff41ea
chore: AI 调用超时从 2min 调整为 12min
m0Nst3r873 Jun 15, 2026
f1df9ef
fix(test): codebase-lint scaffold 使用 tmpdir 作为 source 路径
m0Nst3r873 Jun 15, 2026
8872c60
fix: 修复 MVP 6 个功能闭环问题
m0Nst3r873 Jun 15, 2026
c73f743
feat(tgit): 支持工蜂 group ID 直接用于 --from-org + fallback 策略
m0Nst3r873 Jun 16, 2026
7fcf5ae
feat: 部署 teamai-recall 规则到所有工具 rules/ 目录
m0Nst3r873 Jun 16, 2026
bedaf29
fix: 修复 import --from-org 产物路径、domain 映射和 AI 废话问题
Jun 16, 2026
3550651
feat: recall rules 明确团队知识库必须通过检索 subagent 访问
Jun 16, 2026
a4ad3c1
feat: recall rules 约束 agent 调用顺序 + codebase docs 搜索放宽
Jun 16, 2026
139ffdc
fix: 统一 user/project scope 索引路径与 hook 注入
Jun 16, 2026
d60d7e8
Merge remote-tracking branch 'upstream/main'
m0Nst3r873 Jun 22, 2026
554cd99
Merge branch 'main' of github.com:m0Nst3r873/teamai-cli
m0Nst3r873 Jun 22, 2026
73ee184
fix(uninstall): clean all teamai sections from CLAUDE.md
m0Nst3r873 Jun 22, 2026
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
166 changes: 164 additions & 2 deletions src/__tests__/uninstall.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ import type { TeamaiConfig, LocalConfig } from '../types.js';

const TEAMAI_RULES_START = '<!-- [teamai:rules:start] -->';
const TEAMAI_RULES_END = '<!-- [teamai:rules:end] -->';
const TEAMAI_CULTURE_START = '<!-- [teamai:culture:start] -->';
const TEAMAI_CULTURE_END = '<!-- [teamai:culture:end] -->';
const TEAMAI_CLAUDEMD_START = '<!-- [teamai:claudemd:start] -->';
const TEAMAI_CLAUDEMD_END = '<!-- [teamai:claudemd:end] -->';
const TEAMAI_RECALL_RULES_START = '<!-- [teamai:recall-rules:start] -->';
const TEAMAI_RECALL_RULES_END = '<!-- [teamai:recall-rules:end] -->';
const TEAMAI_ENV_START = '# [teamai:env:start]';
const TEAMAI_ENV_END = '# [teamai:env:end]';

Expand Down Expand Up @@ -107,7 +113,7 @@ async function setupFixture(tmpDir: string) {
hooks: { SessionStart: [{ matcher: '*', hooks: [{ type: 'command', command: 'teamai pull' }], description: '[teamai] Auto-pull' }] },
});

// CLAUDE.md with teamai block + user content
// CLAUDE.md with all teamai section blocks + user content
const claudeMd = [
'# My custom instructions',
'',
Expand All @@ -116,6 +122,21 @@ async function setupFixture(tmpDir: string) {
'## Team Rules (teamai)',
TEAMAI_RULES_END,
'',
TEAMAI_CULTURE_START,
'## Team Culture',
'We value collaboration.',
TEAMAI_CULTURE_END,
'',
TEAMAI_CLAUDEMD_START,
'## Shared Instructions',
'Always use TypeScript.',
TEAMAI_CLAUDEMD_END,
'',
TEAMAI_RECALL_RULES_START,
'## Recall Rules',
'Use teamai-recall subagent.',
TEAMAI_RECALL_RULES_END,
'',
].join('\n');
await fse.writeFile(path.join(homeDir, '.claude', 'CLAUDE.md'), claudeMd);

Expand Down Expand Up @@ -233,7 +254,10 @@ describe('uninstall', () => {

const claudeMd = await fse.readFile(path.join(homeDir, '.claude', 'CLAUDE.md'), 'utf-8');
expect(claudeMd).toContain('# My custom instructions');
expect(claudeMd).not.toContain('teamai');
expect(claudeMd).not.toContain(TEAMAI_RULES_START);
expect(claudeMd).not.toContain(TEAMAI_CULTURE_START);
expect(claudeMd).not.toContain(TEAMAI_CLAUDEMD_START);
expect(claudeMd).not.toContain(TEAMAI_RECALL_RULES_START);
});

it('shell profile 环境变量块被清理', async () => {
Expand Down Expand Up @@ -363,4 +387,142 @@ describe('uninstall', () => {
// User skill preserved
expect(await fse.pathExists(path.join(homeDir, '.claude', 'skills', 'my-own-skill'))).toBe(true);
});

it('清理 CLAUDE.md 中所有 teamai section(culture/claudemd/recall-rules)', async () => {
const { homeDir, repoPath } = await setupFixture(tmpDir);
vi.stubEnv('HOME', homeDir);
vi.stubEnv('SHELL', '/bin/zsh');

const teamConfig = makeTeamConfig();
const localConfig = makeLocalConfig(homeDir, repoPath);
mockAutoDetectInit.mockResolvedValue({ localConfig, teamConfig });

await uninstall({ force: true });

const claudeMd = await fse.readFile(path.join(homeDir, '.claude', 'CLAUDE.md'), 'utf-8');
expect(claudeMd).toContain('# My custom instructions');
expect(claudeMd).not.toContain(TEAMAI_RULES_START);
expect(claudeMd).not.toContain(TEAMAI_RULES_END);
expect(claudeMd).not.toContain(TEAMAI_CULTURE_START);
expect(claudeMd).not.toContain(TEAMAI_CULTURE_END);
expect(claudeMd).not.toContain(TEAMAI_CLAUDEMD_START);
expect(claudeMd).not.toContain(TEAMAI_CLAUDEMD_END);
expect(claudeMd).not.toContain(TEAMAI_RECALL_RULES_START);
expect(claudeMd).not.toContain(TEAMAI_RECALL_RULES_END);
expect(claudeMd).not.toContain('Team Culture');
expect(claudeMd).not.toContain('Shared Instructions');
expect(claudeMd).not.toContain('Recall Rules');
});

it('多工具场景:清理 codebuddy 和 claude-internal 的 CLAUDE.md', async () => {
const { homeDir, repoPath } = await setupFixture(tmpDir);
vi.stubEnv('HOME', homeDir);
vi.stubEnv('SHELL', '/bin/zsh');

// Setup codebuddy CODEBUDDY.md with teamai sections
const codebuddyMd = [
'# CodeBuddy Config',
'',
TEAMAI_RULES_START,
'## Team Rules',
TEAMAI_RULES_END,
'',
TEAMAI_RECALL_RULES_START,
'## Recall Rules',
'Use recall subagent.',
TEAMAI_RECALL_RULES_END,
'',
].join('\n');
await fse.ensureDir(path.join(homeDir, '.codebuddy'));
await fse.writeFile(path.join(homeDir, '.codebuddy', 'CODEBUDDY.md'), codebuddyMd);

// Setup claude-internal CLAUDE.md with teamai sections
const claudeInternalMd = [
'# Internal Config',
'',
TEAMAI_CULTURE_START,
'## Culture',
'Be excellent.',
TEAMAI_CULTURE_END,
'',
TEAMAI_CLAUDEMD_START,
'## Shared',
'Use TypeScript.',
TEAMAI_CLAUDEMD_END,
'',
].join('\n');
await fse.ensureDir(path.join(homeDir, '.claude-internal'));
await fse.writeFile(path.join(homeDir, '.claude-internal', 'CLAUDE.md'), claudeInternalMd);

const teamConfig = makeTeamConfig({
toolPaths: {
claude: {
skills: '.claude/skills',
rules: '.claude/rules',
settings: '.claude/settings.json',
claudemd: '.claude/CLAUDE.md',
},
codebuddy: {
skills: '.codebuddy/skills',
rules: '.codebuddy/rules',
settings: '.codebuddy/settings.json',
claudemd: '.codebuddy/CODEBUDDY.md',
},
'claude-internal': {
skills: '.claude-internal/skills',
rules: '.claude-internal/rules',
settings: '.claude-internal/settings.json',
claudemd: '.claude-internal/CLAUDE.md',
},
},
sharing: {
skills: {},
rules: { enforced: [] },
docs: { localDir: `${path.join(homeDir, '.teamai')}/docs` },
env: { injectShellProfile: true },
},
});
const localConfig = makeLocalConfig(homeDir, repoPath);
mockAutoDetectInit.mockResolvedValue({ localConfig, teamConfig });

await uninstall({ force: true });

// codebuddy: user content preserved, teamai sections removed
const codebuddyResult = await fse.readFile(path.join(homeDir, '.codebuddy', 'CODEBUDDY.md'), 'utf-8');
expect(codebuddyResult).toContain('# CodeBuddy Config');
expect(codebuddyResult).not.toContain(TEAMAI_RULES_START);
expect(codebuddyResult).not.toContain(TEAMAI_RECALL_RULES_START);

// claude-internal: user content preserved, teamai sections removed
const internalResult = await fse.readFile(path.join(homeDir, '.claude-internal', 'CLAUDE.md'), 'utf-8');
expect(internalResult).toContain('# Internal Config');
expect(internalResult).not.toContain(TEAMAI_CULTURE_START);
expect(internalResult).not.toContain(TEAMAI_CLAUDEMD_START);
});

it('仅含 teamai section 的 CLAUDE.md 被整文件删除', async () => {
const homeDir = path.join(tmpDir, 'only-teamai-home');
const repoPath = path.join(tmpDir, 'only-teamai-repo');
await fse.ensureDir(repoPath);
vi.stubEnv('HOME', homeDir);
vi.stubEnv('SHELL', '/bin/bash');

// CLAUDE.md with only teamai content (no user content)
const onlyTeamaiMd = [
TEAMAI_CULTURE_START,
'## Culture',
TEAMAI_CULTURE_END,
].join('\n');
await fse.ensureDir(path.join(homeDir, '.claude'));
await fse.writeFile(path.join(homeDir, '.claude', 'CLAUDE.md'), onlyTeamaiMd);

const teamConfig = makeTeamConfig();
const localConfig = makeLocalConfig(homeDir, repoPath);
mockAutoDetectInit.mockResolvedValue({ localConfig, teamConfig });

await uninstall({ force: true });

// File should be deleted entirely when nothing remains
expect(await fse.pathExists(path.join(homeDir, '.claude', 'CLAUDE.md'))).toBe(false);
});
});
46 changes: 31 additions & 15 deletions src/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { removeHooks } from './hooks.js';
import {
TEAMAI_RULES_START,
TEAMAI_RULES_END,
TEAMAI_CULTURE_START,
TEAMAI_CULTURE_END,
TEAMAI_CLAUDEMD_START,
TEAMAI_CLAUDEMD_END,
TEAMAI_RECALL_RULES_START,
TEAMAI_RECALL_RULES_END,
TEAMAI_ENV_START,
TEAMAI_ENV_END,
getTeamaiHome,
Expand Down Expand Up @@ -52,6 +58,13 @@ interface RemovalPlan {

// ─── Helpers ───────────────────────────────────────────

const CLAUDEMD_MARKER_PAIRS: Array<[string, string]> = [
[TEAMAI_RULES_START, TEAMAI_RULES_END],
[TEAMAI_CULTURE_START, TEAMAI_CULTURE_END],
[TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END],
[TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END],
];

function detectShellProfile(): string | null {
const home = process.env.HOME;
if (!home) return null;
Expand Down Expand Up @@ -140,11 +153,11 @@ async function buildRemovalPlan(
}
}

// (b) CLAUDE.md teamai rules block
// (b) CLAUDE.md teamai section blocks
if (toolPath.claudemd) {
const claudeMdPath = path.join(baseDir, toolPath.claudemd);
const content = await readFileSafe(claudeMdPath);
if (content && content.includes(TEAMAI_RULES_START)) {
if (content && CLAUDEMD_MARKER_PAIRS.some(([start]) => content.includes(start))) {
plan.claudeMdFiles.push(claudeMdPath);
}
}
Expand Down Expand Up @@ -283,24 +296,27 @@ async function executeRemoval(plan: RemovalPlan): Promise<void> {
}
}

// (b) Clean CLAUDE.md teamai rules blocks
// (b) Clean CLAUDE.md teamai section blocks
for (const claudeMdPath of plan.claudeMdFiles) {
try {
const content = await readFileSafe(claudeMdPath);
if (!content) continue;

const startIdx = content.indexOf(TEAMAI_RULES_START);
const endIdx = content.indexOf(TEAMAI_RULES_END);
if (startIdx === -1 || endIdx === -1) continue;

const before = content.substring(0, startIdx).replace(/\n+$/, '\n');
const after = content.substring(endIdx + TEAMAI_RULES_END.length).replace(/^\n+/, '\n');
const newContent = (before + after).trim();
const raw = await readFileSafe(claudeMdPath);
if (!raw) continue;

let content: string = raw;
for (const [startMarker, endMarker] of CLAUDEMD_MARKER_PAIRS) {
const startIdx = content.indexOf(startMarker);
const endIdx = content.indexOf(endMarker);
if (startIdx === -1 || endIdx === -1) continue;

const before = content.substring(0, startIdx).replace(/\n+$/, '\n');
const after = content.substring(endIdx + endMarker.length).replace(/^\n+/, '\n');
content = (before + after).trim();
}

if (newContent.length === 0) {
if (content.length === 0) {
await remove(claudeMdPath);
} else {
await writeFile(claudeMdPath, newContent + '\n');
await writeFile(claudeMdPath, content + '\n');
}
log.success(`清理 CLAUDE.md: ${claudeMdPath}`);
} catch (e) {
Expand Down
Loading