diff --git a/README.md b/README.md index d1eb0aa..7b31c67 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@

License: AGPL-3.0 npm version - Status: stable v0.3.5 + Status: stable v0.3.6 Platforms: Claude Code, Cursor, Codex in ChatGPT desktop and CLI, GitHub Copilot, ZCode Tests: 381 passed

@@ -46,7 +46,7 @@ mancode installs three things: 1. **Hooks** that inject project context, design tokens, and YAGNI checks into agent prompts. -2. **Skills / modes** for `solo`, `/mamba`, `/man`, `/manteam`, `/manps`, and +2. **Skills / modes** for `solo`, `/manba`, `/man`, `/manteam`, `/manps`, and `/mansolo`. 3. **Coaching-staff subagents**: Scout, Plan Coach, Head Coach, Film Analyst (Offense), and Film Analyst (Defense). @@ -80,15 +80,16 @@ After initialization, keep using your coding agent normally. `solo` mode runs by default: practice day, no ceremony. Use `/man` when a task needs planning, testing, and multi-agent review: playoffs, every possession counts. -Invocation is surface-specific. Claude Code and Cursor use `/man`, `/mamba`, +Invocation is surface-specific. Claude Code and Cursor use `/man`, `/manba`, and the other slash-style mode names. Codex in the ChatGPT desktop app, CLI, or -IDE extension loads repo skills from `.agents/skills/`; `$man`, `$mamba`, and +IDE extension loads repo skills from `.agents/skills/`; `$man`, `$manba`, and the other `$` mentions are the portable explicit syntax. In the ChatGPT desktop app, enabled skills also appear in the slash-command list, so a discovered `man` skill can be selected there as `/man`. In CLI/IDE, use `$man` or `/skills`. These are agent skills, not deprecated custom prompts. See the official [skills](https://learn.chatgpt.com/docs/build-skills) and [slash-command](https://learn.chatgpt.com/docs/reference/slash-commands) docs. +Existing workflow metadata remains compatible and does not need migration. ## What Gets Installed @@ -201,7 +202,7 @@ slash commands in your AI coding agent's conversation: | Mode | Best For | What It Does | |---|---|---| | `solo` | Daily coding · practice day | Lightweight hooks, style awareness, YAGNI checks, and one bounded diff self-check | -| `/mamba` | Diagnosis and real validation · Mamba mentality | Reproduces defects, finds root causes, drives real user flows, and runs regression checks | +| `/manba` | Diagnosis and real validation · Mamba mentality | Reproduces defects, finds root causes, drives real user flows, and runs regression checks | | `/man` | Production or high-risk changes · playoffs | Full 9-step workflow with targeted or full risk-based review | | `/manteam` | Team projects · five on the floor, one mind | Shared memory, decisions, coordination, and Conventional Commits | | `/manps` | Cleanup and maintenance · preseason | Project health scan with Markdown and JSON reports | @@ -292,7 +293,7 @@ it should behave, and why previous decisions were made. ## Installation -**Status**: stable v0.3.5. Claude Code, Cursor, Codex in the ChatGPT desktop app +**Status**: stable v0.3.6. Claude Code, Cursor, Codex in the ChatGPT desktop app and CLI, and GitHub Copilot are supported. ZCode adapter support is included, with project skill discovery kept behind a verification gate before release. @@ -342,14 +343,14 @@ mancode install --minimal # Install only solo-mode essentials ```bash # Claude Code / Cursor -/mamba # Diagnose bugs and validate real user flows +/manba # Diagnose bugs and validate real user flows /man # Full 9-step workflow with bounded risk-based review /manps # Project health check /manteam # Team mode and shared memory /mansolo # Return to solo mode # Codex in ChatGPT desktop / CLI / IDE -$mamba +$manba $man $manps $manteam @@ -364,7 +365,7 @@ mancode status mancode status --json mancode install mancode list-platforms -mancode workflow create "" [--parent-task ] +mancode workflow create "" [--parent-task ] mancode workflow update [--step N] [--status in_progress|planned|completed|blocked|abandoned] [--blocking-reason ""] [--outcome fixed|verified|no_repro|manual_test_required] [--plan-version N] [--skipped a,b] mancode workflow review init --review-depth [--review-domain ] mancode workflow review complete --review-domain --report [--blockers Q1,Q2] @@ -385,7 +386,7 @@ mancode version Example output for a UI project (not a default stack): ```text -mancode v0.3.5 +mancode v0.3.6 Project: my-app (React + TypeScript + Tailwind) Mode: solo (default) @@ -447,8 +448,8 @@ mancode status --json ### `mancode workflow` -Creates and manages validated workflow metadata used by `/mamba`, `/man`, and -`/manteam`. A linked `/mamba` child can only be created while its parent is +Creates and manages validated workflow metadata used by `/manba`, `/man`, and +`/manteam`. A linked `/manba` child can only be created while its parent is active at Step 6. Governed review state records required domains, blocker IDs, and the single remediation round. @@ -458,8 +459,8 @@ mancode workflow update --step 4 --plan-version 2 mancode workflow review init --review-depth full mancode workflow review complete --review-domain quality --report film-report-1.md --blockers Q1 mancode workflow review remediate --resolved Q1 -mancode workflow create mamba "verify auth regression" --parent-task -mancode workflow update --status completed --outcome verified +mancode workflow create manba "verify auth regression" --parent-task +mancode workflow update --status completed --outcome verified mancode workflow show --json mancode workflow clean --older-than 30d --dry-run ``` @@ -514,7 +515,7 @@ mancode/ │ ├── Skills │ ├── solo/SKILL.md -│ ├── mamba/SKILL.md +│ ├── manba/SKILL.md │ ├── man/SKILL.md │ ├── manteam/SKILL.md │ ├── manps/SKILL.md @@ -546,7 +547,7 @@ mancode/ | Phase | Focus | |---|---| | MVP-1 | solo mode, aesthetics, and Claude Code hooks | -| MVP-2 | `/mamba`, `/man`, `/manteam`, `/manps`, and coaching-staff subagents | +| MVP-2 | `/manba`, `/man`, `/manteam`, `/manps`, and coaching-staff subagents | | MVP-3 | Cursor, Codex (ChatGPT desktop/CLI), and GitHub Copilot adapters | | Public Release | stable npm release, marketplace distribution, docs, and demos | @@ -581,7 +582,7 @@ markers is preserved. ### ZCode skills not appearing -Ensure `.agents/skills/mamba/SKILL.md` through `.agents/skills/mansolo/SKILL.md` +Ensure `.agents/skills/manba/SKILL.md` through `.agents/skills/mansolo/SKILL.md` exist, then restart or refresh ZCode. ZCode slash commands are not generated yet because the workspace command file path still needs explicit verification. @@ -589,8 +590,8 @@ yet because the workspace command file path still needs explicit verification. Ensure the `.cursor/rules/mancode-*.mdc` files exist. Rules with `alwaysApply: true` (context, practice, solo) load on every conversation. -Mode-specific rules (mamba, man, manteam, manps) trigger based on the -description field — invoke them by asking for `/mamba` or similar. +Mode-specific rules (manba, man, manteam, manps) trigger based on the +description field — invoke them by asking for `/manba` or similar. ### How to do a clean reinstall diff --git a/README.zh-CN.md b/README.zh-CN.md index 23bd8c3..e913ddc 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -17,7 +17,7 @@

许可证:AGPL-3.0 npm 版本 - 状态:稳定版 v0.3.5 + 状态:稳定版 v0.3.6 平台:Claude Code、Cursor、ChatGPT 桌面端 Codex、Codex CLI、GitHub Copilot、ZCode 测试:381 通过

@@ -43,7 +43,7 @@ GitHub Copilot 和 ZCode。Claude Code 获得完整 hooks、skills 和 subagents mancode 会安装三类能力: 1. **Hooks**:在 agent 提示词中注入项目上下文、设计 token 和 YAGNI 检查。 -2. **Skills / modes**:提供 `solo`、`/mamba`、`/man`、`/manteam`、`/manps`、 +2. **Skills / modes**:提供 `solo`、`/manba`、`/man`、`/manteam`、`/manps`、 `/mansolo` 工作流模式。 3. **教练组 subagents**:Scout、Plan Coach、Head Coach、Film Analyst (Offense) 和 Film Analyst (Defense)。 @@ -73,13 +73,14 @@ mancode init 初始化后,继续正常使用你的编码代理。`solo` 默认自动生效:日常训练,零仪式感。遇到需要 计划、测试和多 agent 审查的任务时,使用 `/man`:季后赛,每球必争。 -不同界面的调用方式不同:Claude Code 和 Cursor 使用 `/man`、`/mamba` 等命令; +不同界面的调用方式不同:Claude Code 和 Cursor 使用 `/man`、`/manba` 等命令; ChatGPT 桌面端、Codex CLI 和 Codex IDE 扩展会从 `.agents/skills/` 读取项目 skill, -其中 `$man`、`$mamba` 等 `$` mention 是跨界面稳定的显式调用方式。ChatGPT 桌面端 +其中 `$man`、`$manba` 等 `$` mention 是跨界面稳定的显式调用方式。ChatGPT 桌面端 还会把已启用的 skill 显示在 slash command 列表,因此发现并启用 `man` 后可从列表中 选择 `/man`;CLI/IDE 则使用 `$man` 或 `/skills`。这些属于 agent skills,而不是已弃用 的 custom prompts。参见官方 [skills 文档](https://learn.chatgpt.com/docs/build-skills) 和 [slash command 文档](https://learn.chatgpt.com/docs/reference/slash-commands)。 +已有 workflow 元数据继续兼容,不需要迁移。 ## 安装后创建哪些文件? @@ -172,7 +173,7 @@ mancode 不是 Claude Code、Cursor、Codex 或 Copilot 的替代品。它是在 | 模式 | 适合场景 | 做什么 | |---|---|---| | `solo` | 日常编码 · 日常训练 | 轻量 hooks、风格感知、YAGNI 检查和一次受限 diff 自检 | -| `/mamba` | 诊断与真实验证 · 曼巴心态 | 复现缺陷、定位根因、驱动真实用户路径并执行回归检查 | +| `/manba` | 诊断与真实验证 · 曼巴心态 | 复现缺陷、定位根因、驱动真实用户路径并执行回归检查 | | `/man` | 生产级或高风险改动 · 季后赛 | 完整 9 步工作流和定向/完整风险审查 | | `/manteam` | 团队项目 · 上场五人,一条心 | 共享记忆、决策记录、协作和 Conventional Commits | | `/manps` | 清理和维护 · 季前赛 | 输出 Markdown 和 JSON 项目健康报告 | @@ -253,7 +254,7 @@ src/components/ ## 安装 -**状态**:稳定版 v0.3.5。Claude Code、Cursor、ChatGPT 桌面端中的 Codex、 +**状态**:稳定版 v0.3.6。Claude Code、Cursor、ChatGPT 桌面端中的 Codex、 Codex CLI 和 GitHub Copilot 均已支持。ZCode adapter 已接入,但项目级 skill 发现路径在发布前仍作为验证门禁。 @@ -301,14 +302,14 @@ mancode install --minimal # 只安装 solo 必需文件 ```bash # Claude Code / Cursor -/mamba # 定位 bug 并验证真实用户路径 +/manba # 定位 bug 并验证真实用户路径 /man # 完整 9 步流程和有界风险审查 /manps # 项目健康检查 /manteam # 团队模式和共享记忆 /mansolo # 回到 solo 模式 # ChatGPT 桌面端 Codex / Codex CLI / IDE -$mamba +$manba $man $manps $manteam @@ -323,7 +324,7 @@ mancode status mancode status --json mancode install mancode list-platforms -mancode workflow create "" [--parent-task ] +mancode workflow create "" [--parent-task ] mancode workflow update [--step N] [--status in_progress|planned|completed|blocked|abandoned] [--blocking-reason ""] [--outcome fixed|verified|no_repro|manual_test_required] [--plan-version N] [--skipped a,b] mancode workflow review init --review-depth [--review-domain ] mancode workflow review complete --review-domain --report [--blockers Q1,Q2] @@ -344,7 +345,7 @@ mancode version 以下是 UI 项目的输出示例,并非默认技术栈: ```text -mancode v0.3.5 +mancode v0.3.6 Project: my-app (React + TypeScript + Tailwind) Mode: solo (default) @@ -404,7 +405,7 @@ mancode status --json ### `mancode workflow` -创建和管理 `/mamba`、`/man` 和 `/manteam` 使用的受校验 workflow 元数据。关联 `/mamba` 子任务只能在父任务处于 Step 6 且正在进行时创建;严格模式的 review 状态会记录所需领域、blocker ID 和唯一一轮修复。 +创建和管理 `/manba`、`/man` 和 `/manteam` 使用的受校验 workflow 元数据。关联 `/manba` 子任务只能在父任务处于 Step 6 且正在进行时创建;严格模式的 review 状态会记录所需领域、blocker ID 和唯一一轮修复。 ```bash mancode workflow create man "refactor auth module" @@ -412,8 +413,8 @@ mancode workflow update --step 4 --plan-version 2 mancode workflow review init --review-depth full mancode workflow review complete --review-domain quality --report film-report-1.md --blockers Q1 mancode workflow review remediate --resolved Q1 -mancode workflow create mamba "verify auth regression" --parent-task -mancode workflow update --status completed --outcome verified +mancode workflow create manba "verify auth regression" --parent-task +mancode workflow update --status completed --outcome verified mancode workflow show --json mancode workflow clean --older-than 30d --dry-run ``` @@ -466,7 +467,7 @@ mancode/ │ ├── Skills │ ├── solo/SKILL.md -│ ├── mamba/SKILL.md +│ ├── manba/SKILL.md │ ├── man/SKILL.md │ ├── manteam/SKILL.md │ ├── manps/SKILL.md @@ -494,7 +495,7 @@ mancode/ | 阶段 | 重点 | |---|---| | MVP-1 | solo 模式、审美扫描、Claude Code hooks | -| MVP-2 | `/mamba`、`/man`、`/manteam`、`/manps` 和教练组 subagents | +| MVP-2 | `/manba`、`/man`、`/manteam`、`/manps` 和教练组 subagents | | MVP-3 | Cursor、Codex(ChatGPT 桌面端/CLI)、GitHub Copilot 适配 | | 公开发布 | npm 稳定版、marketplace 分发、文档和演示 | @@ -525,15 +526,15 @@ mancode/ ### ZCode skills 未出现 -确认 `.agents/skills/mamba/SKILL.md` 到 `.agents/skills/mansolo/SKILL.md` +确认 `.agents/skills/manba/SKILL.md` 到 `.agents/skills/mansolo/SKILL.md` 都存在,然后重启或刷新 ZCode。当前尚不生成 ZCode `/man*` slash commands, 因为 workspace command 的文件路径仍需显式验证。 ### Cursor rules 不触发 确认 `.cursor/rules/mancode-*.mdc` 文件存在。`alwaysApply: true` 的规则 -(context、practice、solo)在每次对话加载。模式规则(mamba、man、manteam、 -manps)按 description 触发——输入 `/mamba` 等关键词即可激活。 +(context、practice、solo)在每次对话加载。模式规则(manba、man、manteam、 +manps)按 description 触发——输入 `/manba` 等关键词即可激活。 ### 如何完全重装 diff --git a/docs/12-lifecycle.md b/docs/12-lifecycle.md index c68bd97..2cf9d1b 100644 --- a/docs/12-lifecycle.md +++ b/docs/12-lifecycle.md @@ -107,7 +107,7 @@ Node 直接解析 JSON。不存在 jq fallback,也不会调用任何外部进 ```json { - "version": "0.3.5", + "version": "0.3.6", "currentMode": "solo", "lastMode": "solo", "platform": "claude-code", diff --git a/package-lock.json b/package-lock.json index c8b980a..5823246 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mancode", - "version": "0.3.5", + "version": "0.3.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mancode", - "version": "0.3.5", + "version": "0.3.6", "license": "AGPL-3.0-only", "dependencies": { "commander": "^12.1.0" diff --git a/package.json b/package.json index 6f24d85..f63a324 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mancode", - "version": "0.3.5", + "version": "0.3.6", "description": "AI coding agent workflow harness. Five modes from practice to playoffs: stop over-engineering, reuse project context, and add multi-agent code review.", "type": "module", "license": "AGPL-3.0-only", diff --git a/src/cli.ts b/src/cli.ts index 6c95ad4..1a7f751 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,7 +15,7 @@ import { VERSION } from './version.js'; program .name('mancode') .description( - 'AI coding agent harness. Modes: solo, man, mamba, manteam, manps.', + 'AI coding agent harness. Modes: solo, man, manba, manteam, manps.', ) .version(VERSION); @@ -90,10 +90,10 @@ program .option('--status ', 'Update workflow status') .option( '--parent-task ', - 'Parent /man or /manteam workflow for mamba', + 'Parent /man or /manteam workflow for manba', ) .option('--blocking-reason ', 'Explain why a workflow is blocked') - .option('--outcome ', 'Set mamba outcome') + .option('--outcome ', 'Set manba outcome') .option('--plan-version ', 'Set the next man/manteam plan revision') .option('--skipped ', 'Update skipped steps as comma-separated values') .option('--review-depth ', 'Review depth: targeted or full') diff --git a/src/commands/init.ts b/src/commands/init.ts index a7b289b..ab84cdb 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -48,7 +48,7 @@ const DEFAULT_INIT_PLATFORM: PlatformName = 'claude-code'; * 的 SessionStart hook 读取逻辑(json_get "currentMode")保持一致。 * * MVP-2 新增字段(currentTask / currentWorkflowMode / skippedSteps / - * teamModeAutoDetected / contributors)支持 /man /mamba 流程和团队检测。 + * teamModeAutoDetected / contributors)支持 /man /manba 流程和团队检测。 */ export interface MancodeState { version: string; @@ -836,7 +836,9 @@ function getInitManagedFilePaths( '.mancode/hooks/user-prompt-submit.sh', '.claude/settings.json', '.claude/skills/man8/SKILL.md', + '.claude/skills/mamba/SKILL.md', '.claude/skills/solo/SKILL.md', + '.claude/skills/mancode-mamba.md', '.claude/skills/mancode-solo.md', '.claude/skills/mancode-man8.md', ); @@ -851,7 +853,12 @@ function getInitManagedFilePaths( } } if (platforms.includes('cursor')) { - files.push('.cursor/rules/mancode-man8.mdc', '.cursor/commands/man8.md'); + files.push( + '.cursor/rules/mancode-mamba.mdc', + '.cursor/rules/mancode-man8.mdc', + '.cursor/commands/mamba.md', + '.cursor/commands/man8.md', + ); for (const fileName of MANCODE_CURSOR_RULE_FILES) { files.push(`.cursor/rules/${fileName}`); } @@ -860,19 +867,23 @@ function getInitManagedFilePaths( } } if (platforms.includes('codex') || platforms.includes('zcode')) { - files.push('AGENTS.md', '.agents/skills/man8/SKILL.md'); + files.push( + 'AGENTS.md', + '.agents/skills/mamba/SKILL.md', + '.agents/skills/man8/SKILL.md', + ); for (const mode of MODE_NAMES) { files.push(`.agents/skills/${mode}/SKILL.md`); } } if (platforms.includes('codex')) { - files.push('.codex/skills/man8/SKILL.md'); + files.push('.codex/skills/mamba/SKILL.md', '.codex/skills/man8/SKILL.md'); for (const mode of MODE_NAMES) { files.push(`.codex/skills/${mode}/SKILL.md`); } } if (platforms.includes('zcode')) { - files.push('.zcode/skills/man8/SKILL.md'); + files.push('.zcode/skills/mamba/SKILL.md', '.zcode/skills/man8/SKILL.md'); for (const mode of MODE_NAMES) { files.push(`.zcode/skills/${mode}/SKILL.md`); } @@ -880,6 +891,7 @@ function getInitManagedFilePaths( if (platforms.includes('copilot')) { files.push( '.github/copilot-instructions.md', + '.github/prompts/mamba.prompt.md', '.github/prompts/man8.prompt.md', ); for (const mode of MODE_NAMES) { diff --git a/src/commands/status.ts b/src/commands/status.ts index a6fd0ec..f3da97e 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -493,7 +493,9 @@ function runHookEstimate(rootDir: string, hookPath: string): Promise { * 文本格式输出(默认),字段命名对齐 docs/08-cli-spec.md §4.3。 */ function printText(r: StatusResult): void { - const modeLabel = r.mode === 'solo' ? `${r.mode} (default)` : r.mode; + const publicMode = r.mode === 'mamba' ? 'manba' : r.mode; + const modeLabel = + publicMode === 'solo' ? `${publicMode} (default)` : publicMode; console.log(''); console.log(`mancode v${r.version}`); diff --git a/src/commands/workflow.ts b/src/commands/workflow.ts index 17c581a..e28b2c2 100644 --- a/src/commands/workflow.ts +++ b/src/commands/workflow.ts @@ -12,6 +12,7 @@ import { import { upsertActivePlan } from '../system/team-memory.js'; import { type WorkflowMeta, + type WorkflowMode, createWorkflow, deleteWorkflow, isTerminalWorkflowStatus, @@ -100,7 +101,7 @@ export async function workflow( } else { console.error(`✗ Invalid workflow subcommand: ${subcommand}`); console.error( - ' Use: create | update | review | list | show | clean', + ' Use: create | update | review | list | show | clean', ); } return EXIT_INVALID_ARG; @@ -226,20 +227,21 @@ async function workflowCreate( args: string[], options: WorkflowOptions, ): Promise { - const mode = args[0]; + const requestedMode = args[0]; + const mode = parsePublicWorkflowMode(requestedMode); const task = args.slice(1).join(' ').trim(); - if (mode !== 'man' && mode !== 'mamba' && mode !== 'manteam') { + if (!mode) { return invalidArg( options, - `invalid workflow mode: ${mode ?? ''}`, - 'Use: mancode workflow create [--parent-task ]', + `invalid workflow mode: ${requestedMode ?? ''}`, + 'Use: mancode workflow create [--parent-task ]', ); } if (!task) { return invalidArg( options, 'missing workflow task', - 'Use: mancode workflow create [--parent-task ]', + 'Use: mancode workflow create [--parent-task ]', ); } @@ -322,7 +324,7 @@ async function workflowUpdate( if (existing.mode !== 'mamba' || nextStatus !== 'completed') { return invalidArg( options, - '--outcome is only valid when completing a mamba workflow', + '--outcome is only valid when completing a manba workflow', ); } patch.outcome = options.outcome; @@ -462,7 +464,7 @@ async function workflowShow( console.log(`Workflow: ${meta.taskId}`); console.log(`Task: ${meta.task}`); - console.log(`Mode: ${meta.mode}`); + console.log(`Mode: ${formatPublicWorkflowMode(meta.mode)}`); console.log(`Status: ${meta.status}`); console.log(`Current step:${meta.currentStep}/${maxWorkflowStep(meta.mode)}`); console.log( @@ -476,10 +478,10 @@ async function workflowShow( if (meta.outcome) console.log(`Outcome: ${meta.outcome}`); if (meta.blockingReason) console.log(`Blocked: ${meta.blockingReason}`); - // Show active mamba children for man/manteam workflows (plan §7). + // Show active diagnostic children for man/manteam workflows (plan §7). if (children !== undefined) { if (children.length > 0) { - console.log(`Children: ${children.length} active mamba workflow(s)`); + console.log(`Children: ${children.length} active manba workflow(s)`); for (const child of children) { console.log( ` - ${child.taskId} (Step ${child.currentStep}/5, ${child.status})`, @@ -586,7 +588,19 @@ function formatWorkflowRow(meta: WorkflowView): string { ? `children=${meta.activeChildren.map((child) => child.taskId).join(',')}` : '', ].filter(Boolean); - return `${meta.taskId.padEnd(42)} ${meta.mode.padEnd(7)} ${meta.status.padEnd(11)} ${step}${details.length > 0 ? ` | ${details.join(' | ')}` : ''}`; + return `${meta.taskId.padEnd(42)} ${formatPublicWorkflowMode(meta.mode).padEnd(7)} ${meta.status.padEnd(11)} ${step}${details.length > 0 ? ` | ${details.join(' | ')}` : ''}`; +} + +function parsePublicWorkflowMode( + value: string | undefined, +): WorkflowMode | null { + if (value === 'manba' || value === 'mamba') return 'mamba'; + if (value === 'man' || value === 'manteam') return value; + return null; +} + +function formatPublicWorkflowMode(mode: WorkflowMode): string { + return mode === 'mamba' ? 'manba' : mode; } function attachActiveChildren(workflows: WorkflowMeta[]): WorkflowView[] { diff --git a/src/installers/claude-code.ts b/src/installers/claude-code.ts index 5570792..d09b0f9 100644 --- a/src/installers/claude-code.ts +++ b/src/installers/claude-code.ts @@ -41,6 +41,8 @@ export const LEGACY_CLAUDE_SKILL_SETTINGS: Readonly> = { mansolo: '.claude/skills/mancode-mansolo.md', }; +const LEGACY_MVP2_SKILL_NAMES = ['mamba', 'man8'] as const; + /** * Claude Code 平台安装器。 * @@ -71,6 +73,7 @@ export async function installClaudeCode( // When force is false (auto-repair), only write skills/agents that are // missing — do not overwrite user-customized files. const force = options.force ?? false; + await validateClaudeWriteTargets(claudeDir, options.minimal ?? false, force); await installMancodeCore(projectRoot); @@ -96,6 +99,44 @@ export async function installClaudeCode( await removeLegacyHookFiles(projectRoot); } +async function validateClaudeWriteTargets( + claudeDir: string, + minimal: boolean, + force: boolean, +): Promise { + if (!force) return; + + const skillsDir = path.join(claudeDir, 'skills'); + await assertWritableClaudeSkill(skillsDir, 'solo'); + if (minimal) return; + + for (const skill of MVP2_SKILLS) { + await assertWritableClaudeSkill(skillsDir, skill.name); + } + for (const agent of ALL_AGENTS) { + const agentPath = path.join(claudeDir, 'agents', `${agent.name}.md`); + const existing = await readTextIfExists(agentPath); + if (existing !== null && !isGeneratedClaudeAgent(existing, agent.name)) { + throw new Error( + `refusing to overwrite user-authored Claude Code agent: ${agentPath}`, + ); + } + } +} + +async function assertWritableClaudeSkill( + skillsDir: string, + modeName: string, +): Promise { + const skillPath = path.join(skillsDir, modeName, 'SKILL.md'); + const existing = await readTextIfExists(skillPath); + if (existing !== null && !isGeneratedClaudeSkill(existing, modeName)) { + throw new Error( + `refusing to overwrite user-authored Claude Code skill: ${skillPath}`, + ); + } +} + async function removeLegacyHookFiles(projectRoot: string): Promise { const hooksDir = path.join(projectRoot, '.mancode', 'hooks'); await Promise.all([ @@ -122,7 +163,7 @@ async function installSoloSkill( } /** - * 写入 MVP-2 skill 目录(/man /mamba /mansolo,docs/03)。 + * 写入 MVP-2 skill 目录(/man /manba /mansolo,docs/03)。 * * 目录:.claude/skills//SKILL.md(Claude Code 通过目录名识别命令)。 * `--force` 重装时只覆盖可识别的 mancode 生成文件。 @@ -132,15 +173,18 @@ async function installMvp2Skills( skillsDir: string, force: boolean, ): Promise { - await removeManagedLegacyMan8Skill(skillsDir); for (const skill of MVP2_SKILLS) { await installProjectSkill(skillsDir, skill, force); await removeLegacyFlatSkill(skillsDir, `mancode-${skill.name}`, skill.name); } + await removeManagedLegacyMvp2Skills(skillsDir); } -async function removeManagedLegacyMan8Skill(skillsDir: string): Promise { - await removeGeneratedSkillDir(skillsDir, 'man8'); +async function removeManagedLegacyMvp2Skills(skillsDir: string): Promise { + for (const modeName of LEGACY_MVP2_SKILL_NAMES) { + await removeGeneratedSkillDir(skillsDir, modeName); + await removeLegacyFlatSkill(skillsDir, `mancode-${modeName}`, modeName); + } } async function uninstallMvp2Skills(skillsDir: string): Promise { @@ -148,6 +192,7 @@ async function uninstallMvp2Skills(skillsDir: string): Promise { await removeGeneratedSkillDir(skillsDir, skill.name); await removeLegacyFlatSkill(skillsDir, `mancode-${skill.name}`, skill.name); } + await removeManagedLegacyMvp2Skills(skillsDir); } async function installProjectSkill( @@ -246,7 +291,7 @@ export async function removeClaudeGeneratedContent( const skillsDir = path.join(claudeDir, 'skills'); for (const modeName of [ 'solo', - 'man8', + ...LEGACY_MVP2_SKILL_NAMES, ...MVP2_SKILLS.map((skill) => skill.name), ]) { await removeGeneratedSkillDir(skillsDir, modeName); @@ -296,8 +341,9 @@ function addManagedMarker(content: string, marker: string): string { async function readTextIfExists(filePath: string): Promise { try { return await readFile(filePath, 'utf-8'); - } catch { - return null; + } catch (error) { + if (isNodeError(error) && error.code === 'ENOENT') return null; + throw error; } } diff --git a/src/installers/copilot.ts b/src/installers/copilot.ts index aab7cf9..f4baaa7 100644 --- a/src/installers/copilot.ts +++ b/src/installers/copilot.ts @@ -67,7 +67,7 @@ function renderCopilotPromptConventions(): string { 'GitHub Copilot does not provide native mancode slash commands, hooks, or isolated subagents. Treat these names as user prompt conventions:', '', '- man: use progressive research, planning, implementation, verification, and bounded risk-based review.', - '- mamba: diagnose bugs and validate real user flows or regressions.', + '- manba: diagnose bugs and validate real user flows or regressions.', '- manteam: read team memory and write handoff-friendly summaries.', '- manps: prefer `mancode manps [area]` before cleanup.', '- mansolo: exit any active mode and return to default solo behavior.', diff --git a/src/installers/cursor.ts b/src/installers/cursor.ts index 6bda604..b2324d6 100644 --- a/src/installers/cursor.ts +++ b/src/installers/cursor.ts @@ -12,13 +12,16 @@ export const MANCODE_CURSOR_CORE_RULE_FILES = [ ] as const; export const MANCODE_CURSOR_ADVANCED_RULE_FILES = [ - 'mancode-mamba.mdc', + 'mancode-manba.mdc', 'mancode-man.mdc', 'mancode-manteam.mdc', 'mancode-manps.mdc', ] as const; -export const MANCODE_CURSOR_LEGACY_RULE_FILES = ['mancode-man8.mdc'] as const; +export const MANCODE_CURSOR_LEGACY_RULE_FILES = [ + 'mancode-mamba.mdc', + 'mancode-man8.mdc', +] as const; export const CURSOR_RULE_MANAGED_MARKER = ''; @@ -82,13 +85,12 @@ export async function installCursor( return; } - await removeLegacyCursorRules(projectRoot); await writeRule( rulesDir, - 'mancode-mamba.mdc', + 'mancode-manba.mdc', 'Use for bug diagnosis and real regression testing', false, - renderMambaRule(), + renderManbaRule(), ); await writeRule( rulesDir, @@ -111,6 +113,7 @@ export async function installCursor( false, renderManpsRule(), ); + await removeLegacyCursorRules(projectRoot); await installCursorCommands(projectRoot, options.minimal ?? false); } @@ -165,32 +168,21 @@ export async function removeCursorGeneratedRules( await removeLegacyCursorRules(projectRoot); } -/** Remove the old man8 rule only when it matches mancode's generated body. */ +/** Remove old public mode rules only when they are mancode-generated. */ export async function removeLegacyCursorRules( projectRoot: string, ): Promise { - const legacyPath = path.join( - projectRoot, - '.cursor', - 'rules', - 'mancode-man8.mdc', - ); - try { - const content = await readFile(legacyPath, 'utf-8'); - if ( - content.includes('# mancode man8 — Investigate and Plan') && - content.includes('## Mode Persistence') - ) { - await rm(legacyPath, { force: true }); - } - } catch { - // Missing or unreadable legacy rule: preserve it and continue installation. + const rulesDir = path.join(projectRoot, '.cursor', 'rules'); + for (const fileName of MANCODE_CURSOR_LEGACY_RULE_FILES) { + await removeGeneratedCursorRule(rulesDir, fileName); } } async function removeGeneratedCursorRule( rulesDir: string, - fileName: (typeof MANCODE_CURSOR_RULE_FILES)[number], + fileName: + | (typeof MANCODE_CURSOR_RULE_FILES)[number] + | (typeof MANCODE_CURSOR_LEGACY_RULE_FILES)[number], ): Promise { const rulePath = path.join(rulesDir, fileName); const content = await readTextIfExists(rulePath); @@ -201,7 +193,9 @@ async function removeGeneratedCursorRule( function isGeneratedCursorRule( content: string, - fileName: (typeof MANCODE_CURSOR_RULE_FILES)[number], + fileName: + | (typeof MANCODE_CURSOR_RULE_FILES)[number] + | (typeof MANCODE_CURSOR_LEGACY_RULE_FILES)[number], ): boolean { if (content.includes(CURSOR_RULE_MANAGED_MARKER)) return true; if (fileName === 'mancode-context.mdc') { @@ -230,11 +224,16 @@ function isGeneratedCursorRule( async function readTextIfExists(filePath: string): Promise { try { return await readFile(filePath, 'utf-8'); - } catch { - return null; + } catch (error) { + if (isNodeError(error) && error.code === 'ENOENT') return null; + throw error; } } +function isNodeError(error: unknown): error is NodeJS.ErrnoException { + return error instanceof Error && 'code' in error; +} + function renderPracticeRule(): string { return [ '# mancode Practice', @@ -267,8 +266,8 @@ function renderSoloRule(): string { ].join('\n'); } -function renderMambaRule(): string { - return renderModeSkill('mamba', '/'); +function renderManbaRule(): string { + return renderModeSkill('manba', '/'); } function renderManRule(): string { diff --git a/src/installers/mode-skills.ts b/src/installers/mode-skills.ts index c53cc27..378615c 100644 --- a/src/installers/mode-skills.ts +++ b/src/installers/mode-skills.ts @@ -6,13 +6,13 @@ import path from 'node:path'; * .cursor/commands/, .github/prompts/ */ export const MODE_NAMES = [ - 'mamba', + 'manba', 'man', 'manteam', 'manps', 'mansolo', ] as const; -const LEGACY_MODE_NAMES = ['man8'] as const; +const LEGACY_MODE_NAMES = ['mamba', 'man8'] as const; export type ModeName = (typeof MODE_NAMES)[number]; export const CODEX_SKILL_MANAGED_MARKER = @@ -44,8 +44,8 @@ export const MANCODE_AGENT_SKILL_MARKERS = [ * the coaching staff in sequence within a single conversation. * * @param commandPrefix Platform-specific invocation prefix: - * - Codex uses `$` (e.g. `$mamba`) - * - Cursor uses `/` (e.g. `/mamba`) + * - Codex uses `$` (e.g. `$manba`) + * - Cursor uses `/` (e.g. `/manba`) * - Copilot uses `''` (prompt-file selection, no prefix) */ export function renderModeSkill( @@ -54,7 +54,8 @@ export function renderModeSkill( ): string { const meta = MODE_META[mode]; // mansolo maps to "solo" in state.json — "mansolo" is not a valid currentMode value. - const stateMode = mode === 'mansolo' ? 'solo' : mode; + const stateMode = + mode === 'mansolo' ? 'solo' : mode === 'manba' ? 'mamba' : mode; const modesList = MODE_NAMES.map((m) => `${commandPrefix}${m}`).join(', '); return [ meta.intro, @@ -103,7 +104,6 @@ export async function installCodexSkills( return; } const skillsDir = path.join(projectRoot, '.agents', 'skills'); - await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS); for (const mode of MODE_NAMES) { const modeDir = path.join(skillsDir, mode); await mkdir(modeDir, { recursive: true }); @@ -127,6 +127,7 @@ export async function installCodexSkills( MANCODE_AGENT_SKILL_MARKERS, ); } + await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS); } /** @@ -144,7 +145,6 @@ export async function installZcodeSkills( return; } const skillsDir = path.join(projectRoot, '.agents', 'skills'); - await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS); for (const mode of MODE_NAMES) { const modeDir = path.join(skillsDir, mode); await mkdir(modeDir, { recursive: true }); @@ -168,6 +168,7 @@ export async function installZcodeSkills( MANCODE_AGENT_SKILL_MARKERS, ); } + await removeLegacyManagedSkills(skillsDir, MANCODE_AGENT_SKILL_MARKERS); } /** @@ -183,7 +184,6 @@ export async function installCursorCommands( } const commandsDir = path.join(projectRoot, '.cursor', 'commands'); await mkdir(commandsDir, { recursive: true }); - await removeLegacyGeneratedModeFile(path.join(commandsDir, 'man8.md')); for (const mode of MODE_NAMES) { const meta = MODE_META[mode]; const content = [ @@ -203,6 +203,8 @@ export async function installCursorCommands( 'Cursor', ); } + await removeManagedModeFile(path.join(commandsDir, 'mamba.md'), 'mamba'); + await removeLegacyGeneratedModeFile(path.join(commandsDir, 'man8.md')); } /** @@ -218,7 +220,6 @@ export async function installCopilotPrompts( } const promptsDir = path.join(projectRoot, '.github', 'prompts'); await mkdir(promptsDir, { recursive: true }); - await removeLegacyGeneratedModeFile(path.join(promptsDir, 'man8.prompt.md')); for (const mode of MODE_NAMES) { const meta = MODE_META[mode]; const content = [ @@ -239,6 +240,11 @@ export async function installCopilotPrompts( 'GitHub Copilot', ); } + await removeManagedModeFile( + path.join(promptsDir, 'mamba.prompt.md'), + 'mamba', + ); + await removeLegacyGeneratedModeFile(path.join(promptsDir, 'man8.prompt.md')); } export async function removeCodexSkills(projectRoot: string): Promise { @@ -333,7 +339,7 @@ async function writeManagedModeFile( async function removeManagedModeFile( filePath: string, - mode: ModeName, + mode: string, ): Promise { const content = await readTextIfExists(filePath); if (content && isGeneratedModeFile(content, mode)) { @@ -351,7 +357,7 @@ async function removeLegacyGeneratedModeFile(filePath: string): Promise { } } -function isGeneratedModeFile(content: string, mode: ModeName): boolean { +function isGeneratedModeFile(content: string, mode: string): boolean { return ( content.includes(MODE_FILE_MANAGED_MARKER) || (content.includes(`# mancode ${mode}`) && @@ -446,11 +452,16 @@ function isRecord(value: unknown): value is Record { async function readTextIfExists(filePath: string): Promise { try { return await readFile(filePath, 'utf-8'); - } catch { - return null; + } catch (error) { + if (isNodeError(error) && error.code === 'ENOENT') return null; + throw error; } } +function isNodeError(error: unknown): error is NodeJS.ErrnoException { + return error instanceof Error && 'code' in error; +} + interface ModeMeta { description: string; intro: string; @@ -458,14 +469,14 @@ interface ModeMeta { } const MODE_META: Record = { - mamba: { + manba: { description: 'Diagnose bugs and validate real user flows with targeted regression checks.', - intro: '# mancode mamba — Diagnosis and Real Validation', + intro: '# mancode manba — Diagnosis and Real Validation', workflow: [ '## Five-step workflow (simulate roles in one conversation)', '', - 'Use `mancode workflow create mamba "" --json`; when called from', + 'Use `mancode workflow create manba "" --json`; when called from', 'a /man or /manteam Step 6 workflow, add `--parent-task `. Persist the returned', 'taskId in `.mancode/state.json` as currentTask; set currentMode and', 'currentWorkflowMode to mamba. Never edit metadata.json directly; use', @@ -502,7 +513,7 @@ const MODE_META: Record = { 'Step 3 — Plan: write `plan.md` with scope, reuse, exclusions, risks, completion criteria, and real validation. First plan is v1; revisions use `--plan-version ` and Active Plans is updated by the CLI.', 'Step 4 — Plan gate: the user chooses plan-only (`status planned`), continue to execution, or revise the plan. Do not implement before this choice.', 'Step 5 — Implement the confirmed minimum change.', - 'Step 6 — Run detected build/lint/typecheck/test and smoke checks. A real or complex diagnosis creates a linked mamba child while the parent remains at Step 6. After a resolved child, restore a parent blocked by that child with `workflow update --status in_progress` before Step 7; never auto-resume manual_test_required. Then write `review-scope.md` from the actual diff, verification evidence, and hard-risk triggers. Initialize a targeted review for routine governed changes or a full review for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, cross-service, or infrastructure changes using `mancode workflow review init ...`.', + 'Step 6 — Run detected build/lint/typecheck/test and smoke checks. A real or complex diagnosis creates a linked manba child while the parent remains at Step 6. After a resolved child, restore a parent blocked by that child with `workflow update --status in_progress` before Step 7; never auto-resume manual_test_required. Then write `review-scope.md` from the actual diff, verification evidence, and hard-risk triggers. Initialize a targeted review for routine governed changes or a full review for auth, payment, sensitive data, migrations/deletion, public APIs, untrusted input, concurrency, cross-service, or infrastructure changes using `mancode workflow review init ...`.', 'Step 7 — Run one quality review limited to the changed diff and direct impact. Findings require changed-line evidence and user impact, with at most three new findings. Record stable blocker IDs through `workflow review ... complete`; do not fix yet.', 'Step 8 — Only full review runs the security/boundary reviewer. It must read Film #1, mark the same root cause duplicate, and stay within security, permissions, recovery, resources, and boundaries. A targeted review ends after Film #1.', 'Step 9 — If open blockers exist, fix them in one remediation round and record resolved IDs through `workflow review ... remediate`; with no blockers, do not create a remediation round. Re-run affected verification without re-running completed reviewers. Write `summary.md` and set completed only when required domains are complete and blockers are zero; otherwise set blocked+blockingReason.', @@ -524,9 +535,9 @@ const MODE_META: Record = { '', 'Then execute every /man Step 1 through Step 9, including clarification,', 'plan gate, validation, bounded risk-based review, conditional completion, planVersion,', - 'Active Plans, and linked mamba child behavior. Add compatibility, rollback,', + 'Active Plans, and linked manba child behavior. Add compatibility, rollback,', 'ownership, conflict, and handoff notes at each relevant step.', - 'When a resolved mamba child had blocked the parent, restore the parent through', + 'When a resolved manba child had blocked the parent, restore the parent through', '`workflow update --status in_progress`; never auto-resume manual_test_required.', '', 'Read `.mancode/memory/prd.md`, `spec.md`, and `decisions.md`; append durable', diff --git a/src/installers/shared-content.ts b/src/installers/shared-content.ts index cb3dac6..94c3f50 100644 --- a/src/installers/shared-content.ts +++ b/src/installers/shared-content.ts @@ -79,7 +79,8 @@ function renderProjectContext( const uiLibrary = options.projectProfile ? options.uiLibrary || 'None' : state?.uiLibrary || tokens?.uiLibrary || options.uiLibrary || 'None'; - const mode = state?.currentMode || 'solo'; + const mode = + state?.currentMode === 'mamba' ? 'manba' : state?.currentMode || 'solo'; return [ '## mancode Project Context', @@ -155,7 +156,7 @@ function renderModes(options: SharedContentOptions): string { let commandLabel: string; if (options.capabilities.skills === 'agents-skills') { commandLabel = - 'Invoke mode skills with `$man`, `$mamba`, `$manteam`, `$manps`, `$mansolo`.'; + 'Invoke mode skills with `$man`, `$manba`, `$manteam`, `$manps`, `$mansolo`.'; } else if (options.capabilities.slashCommands === 'native') { commandLabel = 'Use the named commands directly when available.'; } else { @@ -168,7 +169,7 @@ function renderModes(options: SharedContentOptions): string { '', `- solo: default lightweight mode. ${commandLabel}`, '- man: progressive governance workflow with planning and optional full execution.', - '- mamba: diagnose bugs and validate real user flows or regressions.', + '- manba: diagnose bugs and validate real user flows or regressions.', '- manteam: use team memory and leave handoff-friendly summaries.', '- manps: run project health and cleanup scans before remediation.', '- mansolo: return to solo mode.', diff --git a/src/system/workflow.ts b/src/system/workflow.ts index 49ce5e9..c8262f3 100644 --- a/src/system/workflow.ts +++ b/src/system/workflow.ts @@ -13,7 +13,7 @@ import { upsertActivePlan } from './team-memory.js'; /** * Workflow 元数据。 * - * 每个 /man、/mamba 或 /manteam 任务在 .mancode/workflows//metadata.json 里 + * 每个 /man、/manba 或 /manteam 任务在 .mancode/workflows//metadata.json 里 * 存一份,记录任务进度(docs/14-orchestration.md §2.1)。 */ export interface WorkflowMeta { @@ -37,7 +37,7 @@ export interface WorkflowMeta { blockingReason?: string; /** Parent /man or /manteam workflow for a diagnostic child. */ parentTaskId?: string; - /** Final diagnostic result for /mamba workflows. */ + /** Final diagnostic result for /manba workflows. */ outcome?: WorkflowOutcome; /** Monotonically increasing plan revision for /man workflows. */ planVersion?: number; @@ -101,7 +101,7 @@ function slugify(text: string): string { * * @param projectRoot 项目根 * @param task 任务描述 - * @param mode man / mamba / manteam + * @param mode Internal workflow value: man / mamba / manteam. */ export async function createWorkflow( projectRoot: string, @@ -333,7 +333,7 @@ async function validateParentTask( ): Promise { if (!parentTaskId) return; if (mode !== 'mamba') { - throw new Error('only mamba workflows can have a parent task'); + throw new Error('only manba workflows can have a parent task'); } const parent = await readWorkflow(projectRoot, parentTaskId); if (!parent || (parent.mode !== 'man' && parent.mode !== 'manteam')) { @@ -389,7 +389,7 @@ async function validateWorkflowMeta( ); } if (updated.status === 'planned' && updated.mode === 'mamba') { - throw new Error('mamba workflows cannot be planned'); + throw new Error('manba workflows cannot be planned'); } if (updated.status === 'planned' && updated.currentStep !== 4) { throw new Error('planned workflows must be at step 4'); @@ -399,7 +399,7 @@ async function validateWorkflowMeta( updated.currentStep !== maxWorkflowStep(updated.mode) ) { throw new Error( - `completed ${updated.mode} workflows must be at step ${maxWorkflowStep(updated.mode)}`, + `completed ${updated.mode === 'mamba' ? 'manba' : updated.mode} workflows must be at step ${maxWorkflowStep(updated.mode)}`, ); } if ( @@ -416,23 +416,23 @@ async function validateWorkflowMeta( throw new Error('only blocked workflows can have a blocking reason'); } if (updated.mode !== 'mamba' && updated.outcome !== undefined) { - throw new Error('only mamba workflows can have an outcome'); + throw new Error('only manba workflows can have an outcome'); } if (updated.outcome !== undefined && !isWorkflowOutcome(updated.outcome)) { throw new Error(`invalid workflow outcome: ${updated.outcome}`); } if (updated.outcome !== undefined && updated.status !== 'completed') { - throw new Error('mamba outcomes can only be set on completed workflows'); + throw new Error('manba outcomes can only be set on completed workflows'); } if ( updated.mode === 'mamba' && updated.status === 'completed' && !updated.outcome ) { - throw new Error('completed mamba workflows require an outcome'); + throw new Error('completed manba workflows require an outcome'); } if (updated.mode === 'mamba' && updated.planVersion !== undefined) { - throw new Error('mamba workflows cannot have a plan version'); + throw new Error('manba workflows cannot have a plan version'); } if ( updated.reviewPolicyVersion !== undefined && @@ -441,7 +441,7 @@ async function validateWorkflowMeta( throw new Error('invalid workflow review policy version'); } if (updated.mode === 'mamba' && updated.reviewPolicyVersion !== undefined) { - throw new Error('mamba workflows cannot have a review policy version'); + throw new Error('manba workflows cannot have a review policy version'); } if ( updated.planVersion !== undefined && @@ -468,7 +468,7 @@ async function validateWorkflowMeta( updated.status === 'planned') && (await listActiveMambaChildren(projectRoot, updated.taskId)).length > 0 ) { - throw new Error('cannot finish workflow with an active mamba child'); + throw new Error('cannot finish workflow with an active manba child'); } if ( updated.status === 'completed' && @@ -576,7 +576,7 @@ async function listMambaChildren( } /** - * When a /mamba child becomes blocked or completes with manual_test_required, + * When a /manba child becomes blocked or completes with manual_test_required, * the parent /man or /manteam workflow must be synchronously marked blocked * (plan §3 Step 6). This is a no-op if the child is in a non-blocking state * or the parent is already blocked/terminal. @@ -597,8 +597,8 @@ async function propagateChildStatus( const reason = child.status === 'blocked' - ? `mamba child ${child.taskId} is blocked: ${child.blockingReason ?? 'unknown reason'}` - : `mamba child ${child.taskId} requires manual testing`; + ? `manba child ${child.taskId} is blocked: ${child.blockingReason ?? 'unknown reason'}` + : `manba child ${child.taskId} requires manual testing`; await writeMetadata(workflowDir(projectRoot, parent.taskId), { ...parent, diff --git a/src/templates/agents/head-coach.ts b/src/templates/agents/head-coach.ts index ddcd2c0..79267ea 100644 --- a/src/templates/agents/head-coach.ts +++ b/src/templates/agents/head-coach.ts @@ -171,8 +171,8 @@ export const HEAD_COACH_AGENT: AgentSpec = { - 将关键决策交给调用方 appendTeamDecision,并更新 Active Plans。 - 合并 worktree 前取得用户确认;清理临时文件。 -**/mamba 交接阶段**: -- 当真实浏览器验证、复杂复现或回归需要专门诊断时,通过 \`mancode workflow create mamba ... --parent-task \` 创建子 workflow;所有 metadata 变化都走 workflow CLI。 +**/manba 交接阶段**: +- 当真实浏览器验证、复杂复现或回归需要专门诊断时,通过 \`mancode workflow create manba ... --parent-task \` 创建子 workflow;所有 metadata 变化都走 workflow CLI。 - 子任务 fixed/verified/no_repro 后回到父 /man Step 6;若父曾因该子任务 blocked,先用 workflow CLI 恢复为 in_progress。blocked 或 manual_test_required 时让父 workflow 同步 blocked,后者不得自动恢复。 主见强,必要时才问用户。开干。`, diff --git a/src/templates/inline.ts b/src/templates/inline.ts index b348b02..2a63a8f 100644 --- a/src/templates/inline.ts +++ b/src/templates/inline.ts @@ -22,7 +22,8 @@ if (!state) { process.exit(0); } -const mode = text(state.currentMode) || 'solo'; +const storedMode = text(state.currentMode) || 'solo'; +const mode = storedMode === 'mamba' ? 'manba' : storedMode; const techStack = sanitize(state.techStack); const uiLibrary = sanitize(state.uiLibrary); const output = [ diff --git a/src/templates/skills/mamba.ts b/src/templates/skills/mamba.ts index e1feac1..7eb74d7 100644 --- a/src/templates/skills/mamba.ts +++ b/src/templates/skills/mamba.ts @@ -2,16 +2,16 @@ import type { SkillSpec } from './index.js'; import { CORE_CODING_PRINCIPLES } from './principles.js'; export const MAMBA_SKILL: SkillSpec = { - name: 'mamba', + name: 'manba', description: 'Diagnose bugs and validate real user flows. Reproduce, find the root cause, run targeted regression checks, and use browser automation when the platform supports it.', - body: `# mancode · /mamba (Diagnosis & Real Validation) + body: `# mancode · /manba (Diagnosis & Real Validation) -用户用 \`/mamba <问题或模块>\` 触发你。目标是用证据回答:能否复现、根因在哪里、修复是否有效、关键路径是否回归。 +用户用 \`/manba <问题或模块>\` 触发你。目标是用证据回答:能否复现、根因在哪里、修复是否有效、关键路径是否回归。对外模式名是 manba;持久化 workflow/state 为兼容历史数据继续使用内部值 mamba。 ## 1. 建立任务 -先读 state。若 \`currentTask\` 指向 unrelated active workflow,停止并让用户选择恢复该流程或先用 /mansolo 放弃;不得覆盖旧指针。独立任务运行 \`mancode workflow create mamba "<问题或模块>" --json\`;由 /man 或 /manteam Step 6 转入时运行 \`mancode workflow create mamba "<问题或模块>" --parent-task <父taskId> --json\`。读取返回 taskId 后,将 state 的 currentMode/currentTask/currentWorkflowMode 指向 mamba。不得直接创建或改写 metadata.json。 +先读 state。若 \`currentTask\` 指向 unrelated active workflow,停止并让用户选择恢复该流程或先用 /mansolo 放弃;不得覆盖旧指针。独立任务运行 \`mancode workflow create manba "<问题或模块>" --json\`;由 /man 或 /manteam Step 6 转入时运行 \`mancode workflow create manba "<问题或模块>" --parent-task <父taskId> --json\`。读取返回 taskId 后,将 state 的 currentMode/currentTask/currentWorkflowMode 指向内部兼容值 mamba。不得直接创建或改写 metadata.json。 记录现象、环境、复现条件、预期与实际结果、不可触碰的数据。先检查已有测试、路由、模块入口和 README;仍无法确定最小路径时只问关键路径问题。完成后用 CLI 更新至 Step 2。 @@ -26,7 +26,7 @@ export const MAMBA_SKILL: SkillSpec = { 仅当 profile 确认 Web UI 且浏览器能力可用时,检查 \`npx\` 并使用项目的 Playwright CLI wrapper。其他项目类型使用已检测到的 API、模拟器、设备、命令或契约验证方式。浏览器验证时先 snapshot 再使用元素引用;导航、弹窗或 UI 大变化后重新 snapshot。截图、trace 和浏览器日志写入 \`.mancode/workflows//artifacts/playwright/\`,报告只写相对路径,并脱敏 Token、Cookie、个人或业务敏感数据。 -环境就绪后用 CLI 更新至 Step 3;缺少环境、权限或数据则直接写 blocked + blockingReason,并保留 state 指向当前 mamba 以便恢复。 +环境就绪后用 CLI 更新至 Step 3;缺少环境、权限或数据则直接写 blocked + blockingReason,并保留 state 指向当前 manba 以便恢复。 ## 3. 复现与诊断 @@ -46,7 +46,7 @@ export const MAMBA_SKILL: SkillSpec = { - 缺少环境、权限或数据,或修复仍失败:\`status: "blocked"\` + \`blockingReason\` - 受限平台只能生成手测方案:\`status: "completed", outcome: "manual_test_required"\`;必须明确没有执行真实测试 -用 \`mancode workflow update --status [--outcome ] [--blocking-reason <原因>]\` 写结论。父子任务的 blocked/manual_test_required 传播由 CLI 负责。关联子任务以 fixed/verified/no_repro 完成后,先读取父 workflow;若父仍因本子任务 blocked,运行 \`mancode workflow update --status in_progress\` 清除阻塞,再把 state 恢复到父 workflow 的 Step 6。manual_test_required 不得自动恢复父任务。独立任务完成后回 solo;blocked 的独立任务继续保持 mamba state。任何情况下都不得直接编辑 metadata 绕过 CLI。 +用 \`mancode workflow update --status [--outcome ] [--blocking-reason <原因>]\` 写结论。父子任务的 blocked/manual_test_required 传播由 CLI 负责。关联子任务以 fixed/verified/no_repro 完成后,先读取父 workflow;若父仍因本子任务 blocked,运行 \`mancode workflow update --status in_progress\` 清除阻塞,再把 state 恢复到父 workflow 的 Step 6。manual_test_required 不得自动恢复父任务。独立任务完成后回 solo;blocked 的独立任务继续保持 manba 模式(state 内部值为 mamba)。任何情况下都不得直接编辑 metadata 绕过 CLI。 ${CORE_CODING_PRINCIPLES} `, diff --git a/src/templates/skills/man.ts b/src/templates/skills/man.ts index d53416d..35645da 100644 --- a/src/templates/skills/man.ts +++ b/src/templates/skills/man.ts @@ -40,7 +40,7 @@ export const MAN_SKILL: SkillSpec = { ### Step 6: 自测、诊断与回归 -运行实际 build/lint/typecheck/test 和 smoke test。相同代码、环境、命令下相同错误签名失败两次,停止盲试并诊断根因。需要真实浏览器、复杂复现或回归时,用 \`mancode workflow create mamba "<问题>" --parent-task --json\` 创建子 workflow;父任务保持 Step 6。子任务 fixed/verified/no_repro 后恢复本任务;若父曾因该子任务 blocked,先通过 \`workflow update --status in_progress\` 恢复,再更新至 Step 7。blocked 或 manual_test_required 会由 CLI 自动阻塞父任务,不得手改父 metadata,也不得自动越过人工验证要求。 +运行实际 build/lint/typecheck/test 和 smoke test。相同代码、环境、命令下相同错误签名失败两次,停止盲试并诊断根因。需要真实浏览器、复杂复现或回归时,用 \`mancode workflow create manba "<问题>" --parent-task --json\` 创建子 workflow;父任务保持 Step 6。子任务 fixed/verified/no_repro 后恢复本任务;若父曾因该子任务 blocked,先通过 \`workflow update --status in_progress\` 恢复,再更新至 Step 7。blocked 或 manual_test_required 会由 CLI 自动阻塞父任务,不得手改父 metadata,也不得自动越过人工验证要求。 验证后基于**实际 diff**写 \`review-scope.md\`:base、改动文件、需求、已跑验证、硬风险和审查深度。鉴权、支付、敏感数据、迁移/删除、公开 API、未可信输入、并发、跨服务或基础设施命中任一项时用完整审查 \`full\`;否则用定向审查 \`targeted\`。运行 \`mancode workflow review init --review-depth targeted --review-domain quality\` 或 \`--review-depth full\`。用户明确跳过审查时才把 \`review\` 写入累计 skippedSteps,并记录残余风险。 diff --git a/src/templates/skills/mansolo.ts b/src/templates/skills/mansolo.ts index d376e58..ff5b277 100644 --- a/src/templates/skills/mansolo.ts +++ b/src/templates/skills/mansolo.ts @@ -39,8 +39,8 @@ export const MANSOLO_SKILL: SkillSpec = { - **没有 .mancode/state.json**:告诉用户:"项目未初始化,运行 \`mancode init\`。" - **state.json 已是 solo**:仍正常执行,告知用户当前已是 solo。 -- **当前 task 已是 completed/abandoned**:不要尝试把终态改成 abandoned;直接清理 state。若它是关联 mamba 且父任务仍 active,先展示父任务并按下一条确认是否一并放弃。 -- **当前在 active /man /mamba /manteam 流程中**:用 AskUserQuestion 问用户: +- **当前 task 已是 completed/abandoned**:不要尝试把终态改成 abandoned;直接清理 state。若它是关联 manba 且父任务仍 active,先展示父任务并按下一条确认是否一并放弃。 +- **当前在 active /man /manba /manteam 流程中**:用 AskUserQuestion 问用户: \`\`\` AskUserQuestion({ questions: [{ @@ -56,7 +56,7 @@ export const MANSOLO_SKILL: SkillSpec = { \`\`\` 用户确认后: - 先用 \`mancode workflow show --json\` 检查父子关系和 activeChildren。 - - 若存在活跃子任务或当前是带 parentTaskId 的 mamba,明确说明将放弃的 workflow 链;先逐个用 CLI 标记子任务 abandoned,再标记父/当前任务 abandoned。 + - 若存在活跃子任务或当前是带 parentTaskId 的 manba,明确说明将放弃的 workflow 链;先逐个用 CLI 标记子任务 abandoned,再标记父/当前任务 abandoned。 - 所有 metadata 状态都必须通过 \`mancode workflow update --status abandoned\` 更新;CLI 拒绝时停止,不得直接 Edit 绕过。 - 成功后再更新 state.json 切回 solo;CLI 会同步清理 Active Plans。 diff --git a/src/templates/skills/manteam.ts b/src/templates/skills/manteam.ts index 641feab..37c1167 100644 --- a/src/templates/skills/manteam.ts +++ b/src/templates/skills/manteam.ts @@ -55,12 +55,12 @@ export const MANTEAM_SKILL: SkillSpec = { 3. Game Plan:计划里必须包含变更边界、兼容性风险、回滚方式;Plan Coach 只返回计划文本,禁止提前修改业务文件或团队 memory。 4. 计划关卡:用户可只保留计划、继续执行或重写计划;Active Plans 按 taskId 更新。 5. 实施:改动前再次检查 \`git status --short\`,避免踩用户或队友改动。 -6. Self-test:优先跑项目已有验证命令;失败两次停下诊断根因。需要真实验证时创建带 parentTaskId 的 /mamba 子任务。子任务修复后若父任务曾因此 blocked,先用 workflow CLI 恢复父任务为 in_progress;manual_test_required 不得自动恢复。 +6. Self-test:优先跑项目已有验证命令;失败两次停下诊断根因。需要真实验证时创建带 parentTaskId 的 /manba 子任务。子任务修复后若父任务曾因此 blocked,先用 workflow CLI 恢复父任务为 in_progress;manual_test_required 不得自动恢复。 7. Review scope + Film #1:基于实际 diff 写 \`review-scope.md\`,用 \`workflow review ... init\` 选择 targeted 或 full;重点审查行为、可维护性、团队风格与测试,finding 必须有证据和稳定 ID。 8. Film #2:仅 full 执行,先读 Film #1 报告并去重,只审查边界、安全、性能、并发和兼容性。用户明确要求才可跳过全部 review,并写入 \`skippedSteps\` 和残余风险。 9. Post-game:汇总 blocker,只做一轮修复并用 \`workflow review ... remediate\` 记录;复验后写 summary 与 hand-off。验证失败、审查不完整或仍有 blocker 时标记 blocked,不得标 completed。 -如果用户在确认阶段选择"退出"或放弃团队 workflow:用 \`mancode workflow update --status abandoned\` 更新并清理 Active Plans;若有活跃 mamba 子任务,先取得确认并逐个 abandoned。成功后再用 Edit 更新 state 回 solo。不要直接改 metadata,也不要把 abandoned workflow 留在 active state。 +如果用户在确认阶段选择"退出"或放弃团队 workflow:用 \`mancode workflow update --status abandoned\` 更新并清理 Active Plans;若有活跃 manba 子任务,先取得确认并逐个 abandoned。成功后再用 Edit 更新 state 回 solo。不要直接改 metadata,也不要把 abandoned workflow 留在 active state。 ## Team Hand-off Summary diff --git a/tests/codex-adapter.test.ts b/tests/codex-adapter.test.ts index 3dfe380..ebfa794 100644 --- a/tests/codex-adapter.test.ts +++ b/tests/codex-adapter.test.ts @@ -103,7 +103,7 @@ describe('Codex adapter', () => { await silentInit(dir); await install(dir, 'codex'); - for (const mode of ['mamba', 'man', 'manteam', 'manps', 'mansolo']) { + for (const mode of ['manba', 'man', 'manteam', 'manps', 'mansolo']) { const skill = await readFile( path.join(dir, '.agents', 'skills', mode, 'SKILL.md'), 'utf-8', @@ -121,7 +121,7 @@ describe('Codex adapter', () => { await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -132,7 +132,7 @@ describe('Codex adapter', () => { await install(dir, 'codex'); // Verify skills exist before minimal await readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ); @@ -140,7 +140,7 @@ describe('Codex adapter', () => { await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -150,11 +150,11 @@ describe('Codex adapter', () => { await silentInit(dir); await install(dir, 'codex'); - const mamba = await readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + const manba = await readFile( + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ); - expect(mamba).toContain('$mamba'); + expect(manba).toContain('$manba'); }); it('mansolo SKILL.md maps state to solo not mansolo', async () => { @@ -169,22 +169,81 @@ describe('Codex adapter', () => { expect(mansolo).not.toContain('"mansolo"'); }); - it('AGENTS.md includes $mamba invocation guidance for agents-skills', async () => { + it('AGENTS.md includes $manba invocation guidance for agents-skills', async () => { await silentInit(dir); await install(dir, 'codex'); const agents = await readFile(path.join(dir, 'AGENTS.md'), 'utf-8'); - expect(agents).toContain('$mamba'); + expect(agents).toContain('$manba'); + }); + + it('replaces a managed legacy mamba skill with manba during upgrade', async () => { + await silentInit(dir); + const legacyDir = path.join(dir, '.agents', 'skills', 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await mkdir(legacyDir, { recursive: true }); + await writeFile( + legacyPath, + '\n---\nname: mamba\n---\n', + 'utf-8', + ); + + await install(dir, 'codex', { force: true }); + + await expect(readFile(legacyPath, 'utf-8')).rejects.toThrow(); + await expect( + readFile( + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), + 'utf-8', + ), + ).resolves.toContain('name: manba'); + }); + + it('preserves a user-authored legacy mamba skill during upgrade', async () => { + await silentInit(dir); + const legacyDir = path.join(dir, '.agents', 'skills', 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await mkdir(legacyDir, { recursive: true }); + await writeFile(legacyPath, '# user-authored mamba skill\n', 'utf-8'); + + await install(dir, 'codex', { force: true }); + + await expect(readFile(legacyPath, 'utf-8')).resolves.toBe( + '# user-authored mamba skill\n', + ); + }); + + it('keeps the managed legacy mamba skill when manba cannot be written', async () => { + await silentInit(dir); + const skillsDir = path.join(dir, '.agents', 'skills'); + const legacyDir = path.join(skillsDir, 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await mkdir(legacyDir, { recursive: true }); + await writeFile( + legacyPath, + '\n---\nname: mamba\n---\n', + 'utf-8', + ); + await mkdir(path.join(skillsDir, 'manba', 'SKILL.md'), { + recursive: true, + }); + + const code = await install(dir, 'codex', { force: true }); + + expect(code).toBe(EXIT_INSTALL_FAILED); + await expect(readFile(legacyPath, 'utf-8')).resolves.toContain( + 'name: mamba', + ); }); it('refuses to overwrite user-authored same-name Codex skills', async () => { await silentInit(dir); - await mkdir(path.join(dir, '.agents', 'skills', 'mamba'), { + await mkdir(path.join(dir, '.agents', 'skills', 'manba'), { recursive: true, }); await writeFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), - '# custom mamba\n', + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), + '# custom manba\n', 'utf-8', ); @@ -193,20 +252,20 @@ describe('Codex adapter', () => { expect(code).toBe(EXIT_INSTALL_FAILED); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), - ).resolves.toBe('# custom mamba\n'); + ).resolves.toBe('# custom manba\n'); }); it('minimal install preserves user-authored same-name Codex skills', async () => { await silentInit(dir); - await mkdir(path.join(dir, '.agents', 'skills', 'mamba'), { + await mkdir(path.join(dir, '.agents', 'skills', 'manba'), { recursive: true, }); await writeFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), - '# custom mamba\n', + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), + '# custom manba\n', 'utf-8', ); @@ -215,10 +274,10 @@ describe('Codex adapter', () => { expect(code).toBe(EXIT_OK); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), - ).resolves.toBe('# custom mamba\n'); + ).resolves.toBe('# custom manba\n'); }); }); diff --git a/tests/copilot-adapter.test.ts b/tests/copilot-adapter.test.ts index a169cba..b05500b 100644 --- a/tests/copilot-adapter.test.ts +++ b/tests/copilot-adapter.test.ts @@ -3,7 +3,11 @@ import { tmpdir } from 'node:os'; import path from 'node:path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { init } from '../src/commands/init.js'; -import { EXIT_OK, install } from '../src/commands/install.js'; +import { + EXIT_INSTALL_FAILED, + EXIT_OK, + install, +} from '../src/commands/install.js'; import { checkPlatformStatus } from '../src/installers/platform-status.js'; describe('GitHub Copilot adapter', () => { @@ -107,7 +111,7 @@ describe('GitHub Copilot adapter', () => { await silentInit(dir); await install(dir, 'copilot'); - for (const mode of ['mamba', 'man', 'manteam', 'manps', 'mansolo']) { + for (const mode of ['manba', 'man', 'manteam', 'manps', 'mansolo']) { const prompt = await readFile( path.join(dir, '.github', 'prompts', `${mode}.prompt.md`), 'utf-8', @@ -132,12 +136,65 @@ describe('GitHub Copilot adapter', () => { ); }); + it('replaces a managed legacy mamba prompt with manba during upgrade', async () => { + await silentInit(dir); + const promptsDir = path.join(dir, '.github', 'prompts'); + const legacyPath = path.join(promptsDir, 'mamba.prompt.md'); + await mkdir(promptsDir, { recursive: true }); + await writeFile( + legacyPath, + '\n# mancode mamba — Diagnosis\n', + 'utf-8', + ); + + await install(dir, 'copilot', { force: true }); + + await expect(readFile(legacyPath, 'utf-8')).rejects.toThrow(); + await expect( + readFile(path.join(promptsDir, 'manba.prompt.md'), 'utf-8'), + ).resolves.toContain('# mancode manba'); + }); + + it('preserves a user-authored legacy mamba prompt during upgrade', async () => { + await silentInit(dir); + const promptsDir = path.join(dir, '.github', 'prompts'); + const legacyPath = path.join(promptsDir, 'mamba.prompt.md'); + await mkdir(promptsDir, { recursive: true }); + await writeFile(legacyPath, '# user-authored mamba prompt\n', 'utf-8'); + + await install(dir, 'copilot', { force: true }); + + await expect(readFile(legacyPath, 'utf-8')).resolves.toBe( + '# user-authored mamba prompt\n', + ); + }); + + it('keeps the managed legacy mamba prompt when manba cannot be written', async () => { + await silentInit(dir); + const promptsDir = path.join(dir, '.github', 'prompts'); + const legacyPath = path.join(promptsDir, 'mamba.prompt.md'); + await mkdir(promptsDir, { recursive: true }); + await writeFile( + legacyPath, + '\n# mancode mamba — Diagnosis\n', + 'utf-8', + ); + await mkdir(path.join(promptsDir, 'manba.prompt.md')); + + const code = await install(dir, 'copilot', { force: true }); + + expect(code).toBe(EXIT_INSTALL_FAILED); + await expect(readFile(legacyPath, 'utf-8')).resolves.toContain( + '# mancode mamba', + ); + }); + it('minimal force install removes existing prompts', async () => { await silentInit(dir); await install(dir, 'copilot'); // Verify prompts exist before minimal await readFile( - path.join(dir, '.github', 'prompts', 'mamba.prompt.md'), + path.join(dir, '.github', 'prompts', 'manba.prompt.md'), 'utf-8', ); @@ -145,7 +202,7 @@ describe('GitHub Copilot adapter', () => { await expect( readFile( - path.join(dir, '.github', 'prompts', 'mamba.prompt.md'), + path.join(dir, '.github', 'prompts', 'manba.prompt.md'), 'utf-8', ), ).rejects.toThrow(); @@ -173,7 +230,7 @@ describe('GitHub Copilot adapter', () => { it('requires every generated prompt for a non-minimal install', async () => { await silentInit(dir); await install(dir, 'copilot'); - await rm(path.join(dir, '.github', 'prompts', 'mamba.prompt.md')); + await rm(path.join(dir, '.github', 'prompts', 'manba.prompt.md')); const result = await checkPlatformStatus(dir, 'copilot', true); @@ -193,12 +250,12 @@ describe('GitHub Copilot adapter', () => { await silentInit(dir); await install(dir, 'copilot'); - const mamba = await readFile( - path.join(dir, '.github', 'prompts', 'mamba.prompt.md'), + const manba = await readFile( + path.join(dir, '.github', 'prompts', 'manba.prompt.md'), 'utf-8', ); - expect(mamba).not.toContain('$mamba'); - expect(mamba).not.toContain('/mamba'); + expect(manba).not.toContain('$manba'); + expect(manba).not.toContain('/manba'); }); it('mansolo prompt maps state to solo not mansolo', async () => { diff --git a/tests/cursor-adapter.test.ts b/tests/cursor-adapter.test.ts index 1c278bf..9154f9a 100644 --- a/tests/cursor-adapter.test.ts +++ b/tests/cursor-adapter.test.ts @@ -37,8 +37,8 @@ describe('Cursor adapter', () => { const rules = await readdir(path.join(dir, '.cursor', 'rules')); expect(rules.sort()).toEqual([ 'mancode-context.mdc', - 'mancode-mamba.mdc', 'mancode-man.mdc', + 'mancode-manba.mdc', 'mancode-manps.mdc', 'mancode-manteam.mdc', 'mancode-practice.mdc', @@ -117,7 +117,7 @@ describe('Cursor adapter', () => { expect(rules).toContain('mancode-practice.mdc'); expect(rules).toContain('mancode-solo.mdc'); expect(rules).toContain('custom.mdc'); - expect(rules).not.toContain('mancode-mamba.mdc'); + expect(rules).not.toContain('mancode-manba.mdc'); expect(rules).not.toContain('mancode-man.mdc'); expect(rules).not.toContain('mancode-manteam.mdc'); expect(rules).not.toContain('mancode-manps.mdc'); @@ -152,7 +152,7 @@ describe('Cursor adapter', () => { await silentInit(dir); await install(dir, 'cursor'); - for (const mode of ['mamba', 'man', 'manteam', 'manps', 'mansolo']) { + for (const mode of ['manba', 'man', 'manteam', 'manps', 'mansolo']) { const cmd = await readFile( path.join(dir, '.cursor', 'commands', `${mode}.md`), 'utf-8', @@ -190,16 +190,86 @@ describe('Cursor adapter', () => { ); }); + it('replaces managed legacy mamba entries with manba during upgrade', async () => { + await silentInit(dir); + const rulesDir = path.join(dir, '.cursor', 'rules'); + const commandsDir = path.join(dir, '.cursor', 'commands'); + const legacyRule = path.join(rulesDir, 'mancode-mamba.mdc'); + const legacyCommand = path.join(commandsDir, 'mamba.md'); + await mkdir(rulesDir, { recursive: true }); + await mkdir(commandsDir, { recursive: true }); + await writeFile( + legacyRule, + '\n# mancode mamba — Diagnosis\n', + 'utf-8', + ); + await writeFile( + legacyCommand, + '\n# mancode mamba — Diagnosis\n', + 'utf-8', + ); + + await install(dir, 'cursor', { force: true }); + + await expect(readFile(legacyRule, 'utf-8')).rejects.toThrow(); + await expect(readFile(legacyCommand, 'utf-8')).rejects.toThrow(); + await expect( + readFile(path.join(rulesDir, 'mancode-manba.mdc'), 'utf-8'), + ).resolves.toContain('# mancode manba'); + await expect( + readFile(path.join(commandsDir, 'manba.md'), 'utf-8'), + ).resolves.toContain('# mancode manba'); + }); + + it('preserves user-authored legacy mamba entries during upgrade', async () => { + await silentInit(dir); + const legacyRule = path.join(dir, '.cursor', 'rules', 'mancode-mamba.mdc'); + const legacyCommand = path.join(dir, '.cursor', 'commands', 'mamba.md'); + await mkdir(path.dirname(legacyRule), { recursive: true }); + await mkdir(path.dirname(legacyCommand), { recursive: true }); + await writeFile(legacyRule, '# user-authored mamba rule\n', 'utf-8'); + await writeFile(legacyCommand, '# user-authored mamba command\n', 'utf-8'); + + await install(dir, 'cursor', { force: true }); + + await expect(readFile(legacyRule, 'utf-8')).resolves.toBe( + '# user-authored mamba rule\n', + ); + await expect(readFile(legacyCommand, 'utf-8')).resolves.toBe( + '# user-authored mamba command\n', + ); + }); + + it('keeps the managed legacy mamba rule when manba cannot be written', async () => { + await silentInit(dir); + const rulesDir = path.join(dir, '.cursor', 'rules'); + const legacyPath = path.join(rulesDir, 'mancode-mamba.mdc'); + await mkdir(rulesDir, { recursive: true }); + await writeFile( + legacyPath, + '\n# mancode mamba — Diagnosis\n', + 'utf-8', + ); + await mkdir(path.join(rulesDir, 'mancode-manba.mdc')); + + const code = await install(dir, 'cursor', { force: true }); + + expect(code).toBe(EXIT_INSTALL_FAILED); + await expect(readFile(legacyPath, 'utf-8')).resolves.toContain( + '# mancode mamba', + ); + }); + it('minimal force install removes mode command files', async () => { await silentInit(dir); await install(dir, 'cursor'); // Verify commands exist before minimal - await readFile(path.join(dir, '.cursor', 'commands', 'mamba.md'), 'utf-8'); + await readFile(path.join(dir, '.cursor', 'commands', 'manba.md'), 'utf-8'); await install(dir, 'cursor', { force: true, minimal: true }); await expect( - readFile(path.join(dir, '.cursor', 'commands', 'mamba.md'), 'utf-8'), + readFile(path.join(dir, '.cursor', 'commands', 'manba.md'), 'utf-8'), ).rejects.toThrow(); await expect( readFile(path.join(dir, '.cursor', 'commands', 'mansolo.md'), 'utf-8'), @@ -210,12 +280,12 @@ describe('Cursor adapter', () => { await silentInit(dir); await install(dir, 'cursor'); - const mamba = await readFile( - path.join(dir, '.cursor', 'commands', 'mamba.md'), + const manba = await readFile( + path.join(dir, '.cursor', 'commands', 'manba.md'), 'utf-8', ); - expect(mamba).toContain('/mamba'); - expect(mamba).not.toContain('$mamba'); + expect(manba).toContain('/manba'); + expect(manba).not.toContain('$manba'); }); it('mansolo command maps state to solo not mansolo', async () => { diff --git a/tests/hook-budget.test.ts b/tests/hook-budget.test.ts index 03c7090..5edc16b 100644 --- a/tests/hook-budget.test.ts +++ b/tests/hook-budget.test.ts @@ -222,6 +222,20 @@ describe('SessionStart hook team reminder', () => { expect(output).not.toContain('### 团队协作提醒'); expect(output).not.toContain('/manteam '); }); + + it('renders the public manba name for legacy workflow state', async () => { + await writeState(dir, { + currentMode: 'mamba', + teamModeAutoDetected: false, + contributors: 1, + }); + + const output = await runSessionStartHook(dir); + + expect(output).toContain('mancode_mode: manba'); + expect(output).toContain('manba mode'); + expect(output).not.toContain('mamba mode'); + }); }); describe('aesthetic scan hook injection inputs', () => { diff --git a/tests/init.test.ts b/tests/init.test.ts index 8047d37..e6900d7 100644 --- a/tests/init.test.ts +++ b/tests/init.test.ts @@ -107,16 +107,16 @@ describe('mancode init', () => { expect(skillContent).toContain('YAGNI'); // 验证 MVP-2 slash skills 使用 Claude Code 官方目录结构 - const mambaSkill = await readFile( - path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'), + const manbaSkill = await readFile( + path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'), 'utf-8', ); - expect(mambaSkill).toContain('name: mamba'); + expect(manbaSkill).toContain('name: manba'); const skillNames = await readdir(path.join(dir, '.claude', 'skills')); expect(skillNames.sort()).toEqual([ - 'mamba', 'man', + 'manba', 'manps', 'mansolo', 'manteam', @@ -263,7 +263,7 @@ describe('mancode init', () => { expect(agents).not.toContain('mancode Modes'); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -287,10 +287,10 @@ describe('mancode init', () => { ).resolves.toContain('Platform adapter: ZCode'); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), - ).resolves.toContain('name: mamba'); + ).resolves.toContain('name: manba'); await expect( readFile(path.join(dir, '.claude', 'settings.json'), 'utf-8'), ).rejects.toThrow(); @@ -462,10 +462,10 @@ describe('mancode init', () => { dir, '.claude', 'skills', - 'mamba', + 'manba', 'SKILL.md', ); - await writeFile(customSkill, '# user mamba\n', 'utf-8'); + await writeFile(customSkill, '# user manba\n', 'utf-8'); const code = await init(dir, { force: true }); const after = await Promise.all( @@ -475,7 +475,7 @@ describe('mancode init', () => { expect(code).toBe(EXIT_INIT_FAILED); expect(after).toEqual(before); await expect(readFile(customSkill, 'utf-8')).resolves.toBe( - '# user mamba\n', + '# user manba\n', ); }); @@ -667,7 +667,7 @@ describe('mancode init', () => { 'node ".mancode/hooks/user-prompt-submit.mjs"', ); expect(settings.skills.custom).toBe('.claude/skills/custom.md'); - expect(settings.skills.mamba).toBeUndefined(); + expect(settings.skills.manba).toBeUndefined(); await expect( readFile(path.join(mancodeDir, 'hooks', 'session-start.sh'), 'utf-8'), ).rejects.toThrow(); diff --git a/tests/install.test.ts b/tests/install.test.ts index cf47030..5d1ecf9 100644 --- a/tests/install.test.ts +++ b/tests/install.test.ts @@ -59,7 +59,7 @@ describe('mancode install', () => { expect(content).toContain('mancode'); await expect( readFile( - path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).resolves.toContain('Managed by mancode:claude-skill'); @@ -70,16 +70,16 @@ describe('mancode install', () => { it('force install refuses to overwrite user-authored same-name Claude files', async () => { await silentInit(dir); - const skillPath = path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'); + const skillPath = path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'); const agentPath = path.join(dir, '.claude', 'agents', 'scout.md'); - await writeFile(skillPath, '# custom mamba\n', 'utf-8'); + await writeFile(skillPath, '# custom manba\n', 'utf-8'); await writeFile(agentPath, '# custom scout\n', 'utf-8'); const code = await install(dir, 'claude-code', { force: true }); expect(code).toBe(EXIT_INSTALL_FAILED); await expect(readFile(skillPath, 'utf-8')).resolves.toBe( - '# custom mamba\n', + '# custom manba\n', ); await expect(readFile(agentPath, 'utf-8')).resolves.toBe( '# custom scout\n', @@ -101,8 +101,8 @@ describe('mancode install', () => { 'utf-8', ); await writeFile( - path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'), - '# custom mamba\n', + path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'), + '# custom manba\n', 'utf-8', ); @@ -136,6 +136,126 @@ describe('mancode install', () => { ); }); + it('replaces a managed legacy Claude mamba skill with manba during upgrade', async () => { + await silentInit(dir); + const legacyDir = path.join(dir, '.claude', 'skills', 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await mkdir(legacyDir, { recursive: true }); + await writeFile( + legacyPath, + '\n---\nname: mamba\n---\n', + 'utf-8', + ); + + const code = await install(dir, 'claude-code', { force: true }); + + expect(code).toBe(EXIT_OK); + await expect(readFile(legacyPath, 'utf-8')).rejects.toThrow(); + await expect( + readFile( + path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'), + 'utf-8', + ), + ).resolves.toContain('name: manba'); + }); + + it('keeps the managed legacy mamba skill when a forced upgrade conflicts', async () => { + await silentInit(dir); + const skillsDir = path.join(dir, '.claude', 'skills'); + const legacyDir = path.join(skillsDir, 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await rm(path.join(skillsDir, 'manba'), { recursive: true, force: true }); + await mkdir(legacyDir, { recursive: true }); + await writeFile( + legacyPath, + '\n---\nname: mamba\n---\n', + 'utf-8', + ); + await writeFile( + path.join(skillsDir, 'man', 'SKILL.md'), + '# user-authored man skill\n', + 'utf-8', + ); + + const code = await install(dir, 'claude-code', { force: true }); + + expect(code).toBe(EXIT_INSTALL_FAILED); + await expect(readFile(legacyPath, 'utf-8')).resolves.toContain( + 'name: mamba', + ); + await expect( + readFile(path.join(skillsDir, 'manba', 'SKILL.md'), 'utf-8'), + ).rejects.toThrow(); + }); + + it('keeps the managed legacy mamba skill when a Claude agent conflicts', async () => { + await silentInit(dir); + const skillsDir = path.join(dir, '.claude', 'skills'); + const legacyDir = path.join(skillsDir, 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await rm(path.join(skillsDir, 'manba'), { recursive: true, force: true }); + await mkdir(legacyDir, { recursive: true }); + await writeFile( + legacyPath, + '\n---\nname: mamba\n---\n', + 'utf-8', + ); + await writeFile( + path.join(dir, '.claude', 'agents', 'scout.md'), + '# user-authored scout\n', + 'utf-8', + ); + + const code = await install(dir, 'claude-code', { force: true }); + + expect(code).toBe(EXIT_INSTALL_FAILED); + await expect(readFile(legacyPath, 'utf-8')).resolves.toContain( + 'name: mamba', + ); + await expect( + readFile(path.join(skillsDir, 'manba', 'SKILL.md'), 'utf-8'), + ).rejects.toThrow(); + }); + + it('keeps the managed legacy mamba skill when a target path is invalid', async () => { + await silentInit(dir); + const skillsDir = path.join(dir, '.claude', 'skills'); + const legacyDir = path.join(skillsDir, 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await rm(path.join(skillsDir, 'manba'), { recursive: true, force: true }); + await mkdir(legacyDir, { recursive: true }); + await writeFile( + legacyPath, + '\n---\nname: mamba\n---\n', + 'utf-8', + ); + const invalidTarget = path.join(skillsDir, 'man', 'SKILL.md'); + await rm(invalidTarget, { force: true }); + await mkdir(invalidTarget); + + const code = await install(dir, 'claude-code', { force: true }); + + expect(code).toBe(EXIT_INSTALL_FAILED); + await expect(readFile(legacyPath, 'utf-8')).resolves.toContain( + 'name: mamba', + ); + }); + + it('preserves a user-authored legacy Claude mamba skill during upgrade', async () => { + await silentInit(dir); + const legacyDir = path.join(dir, '.claude', 'skills', 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await mkdir(legacyDir, { recursive: true }); + await writeFile(legacyPath, '# user-authored mamba skill\n', 'utf-8'); + + const code = await install(dir, 'claude-code', { force: true }); + + expect(code).toBe(EXIT_OK); + await expect(readFile(legacyPath, 'utf-8')).resolves.toBe( + '# user-authored mamba skill\n', + ); + }); + it('install --minimal --force keeps only solo skill and removes only mancode agents', async () => { await silentInit(dir); const customAgentPath = path.join(dir, '.claude', 'agents', 'custom.md'); @@ -143,7 +263,7 @@ describe('mancode install', () => { expect( await pathExists( - path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'), ), ).toBe(true); expect( @@ -159,7 +279,7 @@ describe('mancode install', () => { expect( await pathExists(path.join(dir, '.claude', 'skills', 'solo', 'SKILL.md')), ).toBe(true); - expect(await pathExists(path.join(dir, '.claude', 'skills', 'mamba'))).toBe( + expect(await pathExists(path.join(dir, '.claude', 'skills', 'manba'))).toBe( false, ); expect( @@ -180,7 +300,7 @@ describe('mancode install', () => { expect( await pathExists(path.join(dir, '.claude', 'skills', 'solo', 'SKILL.md')), ).toBe(true); - expect(await pathExists(path.join(dir, '.claude', 'skills', 'mamba'))).toBe( + expect(await pathExists(path.join(dir, '.claude', 'skills', 'manba'))).toBe( false, ); expect( @@ -190,16 +310,16 @@ describe('mancode install', () => { it('minimal install preserves user-authored same-name Claude files', async () => { await silentInit(dir); - const skillPath = path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'); + const skillPath = path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'); const agentPath = path.join(dir, '.claude', 'agents', 'scout.md'); - await writeFile(skillPath, '# custom mamba\n', 'utf-8'); + await writeFile(skillPath, '# custom manba\n', 'utf-8'); await writeFile(agentPath, '# custom scout\n', 'utf-8'); const code = await install(dir, 'claude-code', { minimal: true }); expect(code).toBe(EXIT_OK); await expect(readFile(skillPath, 'utf-8')).resolves.toBe( - '# custom mamba\n', + '# custom manba\n', ); await expect(readFile(agentPath, 'utf-8')).resolves.toBe( '# custom scout\n', @@ -330,9 +450,9 @@ describe('mancode install', () => { await silentInit(dir); // User customizes a skill - const mambaPath = path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'); - const customContent = '# Custom mamba skill\n\nUser customizations here.\n'; - await writeFile(mambaPath, customContent, 'utf-8'); + const manbaPath = path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'); + const customContent = '# Custom manba skill\n\nUser customizations here.\n'; + await writeFile(manbaPath, customContent, 'utf-8'); // Break readiness by deleting settings.json await rm(path.join(dir, '.claude', 'settings.json'), { force: true }); @@ -348,8 +468,8 @@ describe('mancode install', () => { expect(settings.hooks.SessionStart).toBeDefined(); // User-customized skill should be preserved (not overwritten) - const mambaContent = await readFile(mambaPath, 'utf-8'); - expect(mambaContent).toBe(customContent); + const manbaContent = await readFile(manbaPath, 'utf-8'); + expect(manbaContent).toBe(customContent); }); it('returns EXIT_OK when already installed (idempotent, no --force)', async () => { @@ -494,7 +614,7 @@ describe('mancode install', () => { expect(config.platformOptions.codex.minimal).toBe(true); expect( await pathExists( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), ), ).toBe(false); }); @@ -590,7 +710,7 @@ describe('mancode install', () => { const configPath = path.join(dir, '.mancode', 'config.json'); const logPath = path.join(dir, '.mancode', 'logs', 'hooks.log'); const hookPath = path.join(dir, '.mancode', 'hooks', 'session-start.mjs'); - const skillPath = path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'); + const skillPath = path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'); const agentPath = path.join(dir, '.claude', 'agents', 'scout.md'); const invalidSettings = '{ invalid json'; const existingConfig = { diff --git a/tests/shared-content.test.ts b/tests/shared-content.test.ts index e7646f7..f604e24 100644 --- a/tests/shared-content.test.ts +++ b/tests/shared-content.test.ts @@ -48,12 +48,32 @@ describe('generateSharedContent', () => { expect(content).toContain('read `.mancode/state.json`'); expect(content).toContain('read `.mancode/aesthetics/style-tokens.json`'); expect(content).toContain('YAGNI ladder'); - expect(content).toContain('mamba: diagnose bugs'); + expect(content).toContain('manba: diagnose bugs'); expect(content).toContain('Platform Downgrade'); expect(content).toContain('Simulate the coaching staff'); expect(content).toContain('Scout, Plan Coach, Head Coach'); }); + it('renders the public manba name for legacy workflow state', async () => { + await writeState({ currentMode: 'mamba' }); + + const content = await generateSharedContent(dir, { + platform: 'codex', + displayName: 'Codex (ChatGPT desktop/CLI)', + capabilities: { + slashCommands: 'partial', + subagents: false, + hooks: false, + skills: 'agents-skills', + }, + techStack: [], + uiLibrary: null, + }); + + expect(content).toContain('Current mode: manba'); + expect(content).not.toContain('Current mode: mamba'); + }); + it('uses the live install profile instead of stale persisted context', async () => { await writeState({ currentMode: 'solo', diff --git a/tests/skills.test.ts b/tests/skills.test.ts index 3a2fa82..1519373 100644 --- a/tests/skills.test.ts +++ b/tests/skills.test.ts @@ -25,8 +25,8 @@ describe('mvp-2 skills', () => { it('ships the redesigned command set without man8', () => { expect(MVP2_SKILLS.map((skill) => skill.name).sort()).toEqual([ - 'mamba', 'man', + 'manba', 'manps', 'mansolo', 'manteam', @@ -62,14 +62,14 @@ describe('mvp-2 skills', () => { expect(SOLO_SKILL).not.toMatch(/自审发现 3 个以上问题/); }); - it('defines mamba diagnosis and real browser validation boundaries', () => { + it('defines manba diagnosis and real browser validation boundaries', () => { expect(MAMBA_SKILL.body).toMatch(/diagnosis\.md/); expect(MAMBA_SKILL.body).toMatch(/mamba-report\.md/); expect(MAMBA_SKILL.body).toMatch(/Playwright/); expect(MAMBA_SKILL.body).toMatch(/snapshot/); expect(MAMBA_SKILL.body).toMatch(/manual_test_required/); expect(MAMBA_SKILL.body).toMatch(/profile 确认 Web UI/); - expect(MAMBA_SKILL.body).toMatch(/workflow create mamba/); + expect(MAMBA_SKILL.body).toMatch(/workflow create manba/); expect(MAMBA_SKILL.body).toMatch(/--parent-task/); expect(MAMBA_SKILL.body).toMatch( /currentMode\/currentTask\/currentWorkflowMode/, @@ -85,7 +85,7 @@ describe('mvp-2 skills', () => { expect(MANTEAM_SKILL.body).toMatch(/handoff\.md/); expect(MANSOLO_SKILL.body).toMatch(/abandoned/); expect(MANPS_SKILL.body).toMatch(/Preseason/); - expect(MANSOLO_SKILL.body).toMatch(/mamba/); + expect(MANSOLO_SKILL.body).toMatch(/manba/); expect(MANSOLO_SKILL.body).toMatch(/workflow show/); expect(MANSOLO_SKILL.body).toMatch(/workflow update.*abandoned/); expect(MANSOLO_SKILL.body).toMatch(/completed\/abandoned/); @@ -106,20 +106,21 @@ describe('mvp-2 skills', () => { expect(man).toMatch(/one remediation round/); expect(man).toMatch(/workflow review/); - const mamba = renderModeSkill('mamba', '/'); - expect(mamba).toMatch(/workflow create mamba/); - expect(mamba).toMatch(/--parent-task/); - expect(mamba).toMatch(/diagnosis\.md/); - expect(mamba).toMatch(/mamba-report\.md/); - expect(mamba).toMatch(/manual_test_required/); - expect(mamba).toMatch(/production writes require explicit approval/); - expect(mamba).toMatch(/--status in_progress/); - expect(mamba).toMatch(/Never auto-resume manual_test_required/); - expect(mamba).toMatch(/unrelated active workflow/); + const manba = renderModeSkill('manba', '/'); + expect(manba).toMatch(/workflow create manba/); + expect(manba).toMatch(/currentWorkflowMode to mamba/); + expect(manba).toMatch(/--parent-task/); + expect(manba).toMatch(/diagnosis\.md/); + expect(manba).toMatch(/mamba-report\.md/); + expect(manba).toMatch(/manual_test_required/); + expect(manba).toMatch(/production writes require explicit approval/); + expect(manba).toMatch(/--status in_progress/); + expect(manba).toMatch(/Never auto-resume manual_test_required/); + expect(manba).toMatch(/unrelated active workflow/); const manteam = renderModeSkill('manteam', '/'); expect(manteam).toMatch(/Step 1 through Step 9/); - expect(manteam).toMatch(/linked mamba child/); + expect(manteam).toMatch(/linked manba child/); const mansolo = renderModeSkill('mansolo', '/'); expect(mansolo).toMatch(/workflow show/); @@ -128,8 +129,8 @@ describe('mvp-2 skills', () => { it('renders skill frontmatter', () => { const rendered = renderSkill(MAMBA_SKILL); - expect(rendered).toContain('name: mamba'); - expect(rendered).toContain('# mancode · /mamba'); + expect(rendered).toContain('name: manba'); + expect(rendered).toContain('# mancode · /manba'); }); it('installs the redesigned Claude skills', async () => { diff --git a/tests/status.test.ts b/tests/status.test.ts index cd38d2f..fa89a56 100644 --- a/tests/status.test.ts +++ b/tests/status.test.ts @@ -98,7 +98,7 @@ describe('mancode status', () => { it('reports a full Claude adapter incomplete when a generated skill is missing', async () => { await silentInit(dir); - await rm(path.join(dir, '.claude', 'skills', 'mamba'), { + await rm(path.join(dir, '.claude', 'skills', 'manba'), { recursive: true, force: true, }); @@ -327,7 +327,7 @@ describe('mancode status', () => { it('reports a non-minimal Cursor adapter incomplete when a mode command is missing', async () => { await silentInit(dir); await install(dir, 'cursor'); - await rm(path.join(dir, '.cursor', 'commands', 'mamba.md')); + await rm(path.join(dir, '.cursor', 'commands', 'manba.md')); const logs = await captureLog(() => status(dir, { json: true })); const result: StatusResult = JSON.parse(logs.join('\n')); diff --git a/tests/uninstall.test.ts b/tests/uninstall.test.ts index 1f2a499..ebf996e 100644 --- a/tests/uninstall.test.ts +++ b/tests/uninstall.test.ts @@ -133,7 +133,7 @@ describe('mancode uninstall', () => { expect(code).toBe(EXIT_OK); await expect( - readFile(path.join(dir, '.cursor', 'commands', 'mamba.md'), 'utf-8'), + readFile(path.join(dir, '.cursor', 'commands', 'manba.md'), 'utf-8'), ).rejects.toThrow(); await expect( readFile(path.join(dir, '.cursor', 'commands', 'mansolo.md'), 'utf-8'), @@ -149,7 +149,7 @@ describe('mancode uninstall', () => { await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -165,8 +165,8 @@ describe('mancode uninstall', () => { await silentInit(dir); await install(dir, 'codex'); await writeFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), - '# custom mamba\n', + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), + '# custom manba\n', 'utf-8', ); @@ -175,10 +175,10 @@ describe('mancode uninstall', () => { expect(code).toBe(EXIT_OK); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), - ).resolves.toBe('# custom mamba\n'); + ).resolves.toBe('# custom manba\n'); }); it('also removes legacy .codex/skills/ managed files from pre-fix versions', async () => { @@ -226,7 +226,7 @@ describe('mancode uninstall', () => { dir, '.agents', 'skills', - 'mamba', + 'manba', 'SKILL.md', ); @@ -247,7 +247,7 @@ describe('mancode uninstall', () => { await install(dir, 'zcode', { force: true, minimal: true }); - const skillPath = path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'); + const skillPath = path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'); await expect(readFile(skillPath, 'utf-8')).resolves.toContain( 'Managed by mancode:zcode-skill', ); @@ -265,7 +265,7 @@ describe('mancode uninstall', () => { await expect( readFile( - path.join(dir, '.github', 'prompts', 'mamba.prompt.md'), + path.join(dir, '.github', 'prompts', 'manba.prompt.md'), 'utf-8', ), ).rejects.toThrow(); @@ -316,7 +316,7 @@ describe('mancode uninstall', () => { expect(await readFile(customAgentPath, 'utf-8')).toBe('# custom agent\n'); await expect( readFile( - path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -324,16 +324,16 @@ describe('mancode uninstall', () => { it('preserves user-authored same-name Claude skills and agents', async () => { await silentInit(dir); - const skillPath = path.join(dir, '.claude', 'skills', 'mamba', 'SKILL.md'); + const skillPath = path.join(dir, '.claude', 'skills', 'manba', 'SKILL.md'); const agentPath = path.join(dir, '.claude', 'agents', 'scout.md'); - await writeFile(skillPath, '# custom mamba\n', 'utf-8'); + await writeFile(skillPath, '# custom manba\n', 'utf-8'); await writeFile(agentPath, '# custom scout\n', 'utf-8'); const code = await uninstall(dir, 'claude-code', { force: true }); expect(code).toBe(EXIT_OK); await expect(readFile(skillPath, 'utf-8')).resolves.toBe( - '# custom mamba\n', + '# custom manba\n', ); await expect(readFile(agentPath, 'utf-8')).resolves.toBe( '# custom scout\n', @@ -536,7 +536,7 @@ describe('mancode uninstall', () => { ).toBe(false); expect( await pathExists( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), ), ).toBe(false); }); diff --git a/tests/workflow-command.test.ts b/tests/workflow-command.test.ts index 6b5bda8..258d661 100644 --- a/tests/workflow-command.test.ts +++ b/tests/workflow-command.test.ts @@ -158,6 +158,18 @@ describe('mancode workflow command', () => { }); }); + it('workflow create reports the original invalid mode', async () => { + const logs = await captureLog(() => + workflow(dir, 'create', ['unknown-mode', 'task']), + ); + + expect(logs.code).toBe(EXIT_INVALID_ARG); + expect(logs.stderr.join('\n')).toContain( + 'invalid workflow mode: unknown-mode', + ); + expect(logs.stderr.join('\n')).toContain(''); + }); + it('workflow update rejects a blocking reason for a non-blocked status', async () => { const meta = await createWorkflow(dir, 'invalid blocking reason', 'man'); @@ -265,7 +277,7 @@ describe('mancode workflow command', () => { const output = logs.stdout.join('\n'); expect(output).toContain(`Workflow: ${meta.taskId}`); expect(output).toContain('Task: fix login bug'); - expect(output).toContain('Mode: mamba'); + expect(output).toContain('Mode: manba'); }); it('workflow show displays the current plan version for governed workflows', async () => { @@ -293,7 +305,7 @@ describe('mancode workflow command', () => { expect(shown.activeChildren[0].taskId).toBe(child.taskId); }); - it('creates and completes linked mamba workflows through the CLI contract', async () => { + it('creates and completes linked manba workflows through the CLI contract', async () => { const parentLogs = await captureLog(() => workflow(dir, 'create', ['man', 'parent', 'implementation'], { json: true, @@ -305,7 +317,7 @@ describe('mancode workflow command', () => { ); const childLogs = await captureLog(() => - workflow(dir, 'create', ['mamba', 'verify', 'regression'], { + workflow(dir, 'create', ['manba', 'verify', 'regression'], { parentTask: parent.taskId, json: true, }), @@ -322,6 +334,7 @@ describe('mancode workflow command', () => { const completed = JSON.parse(completedLogs.stdout.join('\n')); expect(childLogs.code).toBe(EXIT_OK); + expect(child.mode).toBe('mamba'); expect(child.parentTaskId).toBe(parent.taskId); expect(completedLogs.code).toBe(EXIT_OK); expect(completed).toMatchObject({ @@ -331,7 +344,7 @@ describe('mancode workflow command', () => { }); }); - it('accepts every documented standalone mamba outcome through the CLI', async () => { + it('accepts every documented standalone manba outcome through the CLI', async () => { for (const outcome of [ 'fixed', 'verified', @@ -339,7 +352,7 @@ describe('mancode workflow command', () => { 'manual_test_required', ]) { const createdLogs = await captureLog(() => - workflow(dir, 'create', ['mamba', `outcome-${outcome}`], { + workflow(dir, 'create', ['manba', `outcome-${outcome}`], { json: true, }), ); @@ -359,13 +372,29 @@ describe('mancode workflow command', () => { } }); - it('propagates a CLI-blocked mamba child to its parent', async () => { + it('accepts the legacy mamba spelling without exposing it in text output', async () => { + const createdLogs = await captureLog(() => + workflow(dir, 'create', ['mamba', 'legacy', 'spelling'], { json: true }), + ); + const created = JSON.parse(createdLogs.stdout.join('\n')); + + const shownLogs = await captureLog(() => + workflow(dir, 'show', [created.taskId]), + ); + + expect(createdLogs.code).toBe(EXIT_OK); + expect(created.mode).toBe('mamba'); + expect(shownLogs.stdout.join('\n')).toContain('Mode: manba'); + expect(shownLogs.stdout.join('\n')).not.toContain('Mode: mamba'); + }); + + it('propagates a CLI-blocked manba child to its parent', async () => { const parent = await createWorkflow(dir, 'parent for blocked child', 'man'); await captureLog(() => workflow(dir, 'update', [parent.taskId], { step: '6' }), ); const childLogs = await captureLog(() => - workflow(dir, 'create', ['mamba', 'blocked-child'], { + workflow(dir, 'create', ['manba', 'blocked-child'], { parentTask: parent.taskId, json: true, }), diff --git a/tests/workflow.test.ts b/tests/workflow.test.ts index 03d7a6b..b84ec42 100644 --- a/tests/workflow.test.ts +++ b/tests/workflow.test.ts @@ -143,7 +143,7 @@ describe('workflow helpers', () => { createWorkflow(dir, 'invalid child', 'man', { parentTaskId: parent.taskId, }), - ).rejects.toThrow(/only mamba/); + ).rejects.toThrow(/only manba/); }); it('rejects a mamba child unless its parent is active at step 6', async () => { @@ -305,7 +305,7 @@ describe('workflow helpers', () => { currentStep: 9, status: 'completed', }), - ).rejects.toThrow(/active mamba child/); + ).rejects.toThrow(/active manba child/); }); it('rejects lifecycle states before their required workflow step', async () => { diff --git a/tests/zcode-adapter.test.ts b/tests/zcode-adapter.test.ts index 98feff3..ab30c27 100644 --- a/tests/zcode-adapter.test.ts +++ b/tests/zcode-adapter.test.ts @@ -33,7 +33,7 @@ describe('ZCode adapter', () => { expect(agents).toContain(''); expect(agents).toContain('# mancode Configuration'); expect(agents).toContain('Platform adapter: ZCode'); - expect(agents).toContain('$mamba'); + expect(agents).toContain('$manba'); expect(agents).toContain('one bounded self-check'); expect(agents).toContain('Do not start another reviewer'); @@ -65,7 +65,7 @@ describe('ZCode adapter', () => { await silentInit(dir); await install(dir, 'zcode'); - for (const mode of ['mamba', 'man', 'manteam', 'manps', 'mansolo']) { + for (const mode of ['manba', 'man', 'manteam', 'manps', 'mansolo']) { const skill = await readFile( path.join(dir, '.agents', 'skills', mode, 'SKILL.md'), 'utf-8', @@ -74,7 +74,7 @@ describe('ZCode adapter', () => { expect(skill).toContain('Managed by mancode:zcode-skill'); expect(skill).toContain('Mode Persistence'); expect(skill).toContain('YAGNI ladder'); - expect(skill).toContain('$mamba'); + expect(skill).toContain('$manba'); } }); @@ -84,7 +84,7 @@ describe('ZCode adapter', () => { await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -94,7 +94,7 @@ describe('ZCode adapter', () => { await silentInit(dir); await install(dir, 'zcode'); await readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ); @@ -102,7 +102,7 @@ describe('ZCode adapter', () => { await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -164,7 +164,7 @@ describe('ZCode adapter', () => { it('status reports ZCode not ready when a generated skill is missing', async () => { await silentInit(dir); await install(dir, 'zcode'); - await rm(path.join(dir, '.agents', 'skills', 'mamba'), { + await rm(path.join(dir, '.agents', 'skills', 'manba'), { recursive: true, force: true, }); @@ -193,14 +193,37 @@ describe('ZCode adapter', () => { expect(result.platformStatus.zcode.detail).toContain('missing'); }); + it('keeps the managed legacy mamba skill when manba cannot be written', async () => { + await silentInit(dir); + const skillsDir = path.join(dir, '.agents', 'skills'); + const legacyDir = path.join(skillsDir, 'mamba'); + const legacyPath = path.join(legacyDir, 'SKILL.md'); + await mkdir(legacyDir, { recursive: true }); + await writeFile( + legacyPath, + '\n---\nname: mamba\n---\n', + 'utf-8', + ); + await mkdir(path.join(skillsDir, 'manba', 'SKILL.md'), { + recursive: true, + }); + + const code = await install(dir, 'zcode', { force: true }); + + expect(code).toBe(EXIT_INSTALL_FAILED); + await expect(readFile(legacyPath, 'utf-8')).resolves.toContain( + 'name: mamba', + ); + }); + it('refuses to overwrite user-authored same-name ZCode skills', async () => { await silentInit(dir); - await mkdir(path.join(dir, '.agents', 'skills', 'mamba'), { + await mkdir(path.join(dir, '.agents', 'skills', 'manba'), { recursive: true, }); await writeFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), - '# custom mamba\n', + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), + '# custom manba\n', 'utf-8', ); @@ -209,20 +232,20 @@ describe('ZCode adapter', () => { expect(code).toBe(EXIT_INSTALL_FAILED); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), - ).resolves.toBe('# custom mamba\n'); + ).resolves.toBe('# custom manba\n'); }); it('minimal install preserves user-authored same-name ZCode skills', async () => { await silentInit(dir); - await mkdir(path.join(dir, '.agents', 'skills', 'mamba'), { + await mkdir(path.join(dir, '.agents', 'skills', 'manba'), { recursive: true, }); await writeFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), - '# custom mamba\n', + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), + '# custom manba\n', 'utf-8', ); @@ -231,10 +254,10 @@ describe('ZCode adapter', () => { expect(code).toBe(EXIT_OK); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), - ).resolves.toBe('# custom mamba\n'); + ).resolves.toBe('# custom manba\n'); }); it('coexists with Codex AGENTS.md managed block', async () => { @@ -318,7 +341,7 @@ describe('ZCode adapter', () => { expect(agents).not.toContain(''); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), ).rejects.toThrow(); @@ -331,8 +354,8 @@ describe('ZCode adapter', () => { await silentInit(dir); await install(dir, 'zcode'); await writeFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), - '# custom mamba\n', + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), + '# custom manba\n', 'utf-8', ); @@ -341,10 +364,10 @@ describe('ZCode adapter', () => { expect(code).toBe(EXIT_OK); await expect( readFile( - path.join(dir, '.agents', 'skills', 'mamba', 'SKILL.md'), + path.join(dir, '.agents', 'skills', 'manba', 'SKILL.md'), 'utf-8', ), - ).resolves.toBe('# custom mamba\n'); + ).resolves.toBe('# custom manba\n'); }); }); diff --git a/website/PLAN.md b/website/PLAN.md index 2a32f3f..fc9e694 100644 --- a/website/PLAN.md +++ b/website/PLAN.md @@ -17,7 +17,7 @@ The page should feel like a live engineering broadcast: industrial, restrained, Only claims already documented in the repository will appear on the page: -- Five working modes: `solo`, `/mamba`, `/man`, `/manteam`, and `/manps`. +- Five working modes: `solo`, `/manba`, `/man`, `/manteam`, and `/manps`. - `/man` uses a nine-step workflow with targeted or full risk-based review and one remediation round. - mancode detects project context and UI design signals when relevant. - Workflow artifacts and team memory stay in the repository. diff --git a/website/docs.html b/website/docs.html index df8c642..5326cc1 100644 --- a/website/docs.html +++ b/website/docs.html @@ -24,7 +24,7 @@
mancode - Documentation / v0.3.5 + Documentation / v0.3.6
@@ -124,7 +124,7 @@

CLI commands

mancode status [--json]

Inspect

Shows the current mode, detected stack, installed platforms, and readiness. Claude Code installations also report hook registration and estimated injection size.

  • Use plain output for a quick human check.
  • Use --json for scripts or CI.

mancode install <platform>

Adapter

Installs or repairs one platform adapter after initialization.

  • Platforms: claude-code, cursor, codex, copilot, zcode.
  • Use --force to rewrite generated configuration.
  • Use --minimal for solo-mode essentials only.

mancode list-platforms

Discover

Lists the adapters this version knows how to install. Use this before scripting platform selection.

-

mancode workflow …

Track

Creates and manages validated metadata for man, mamba, and manteam workflow runs.

  • create <mode> "<task>" starts a record.
  • update <taskId> advances steps or status.
  • review <taskId> … enforces required domains, blockers, and one remediation round.
  • list, show, and clean inspect and maintain records.
+

mancode workflow …

Track

Creates and manages validated metadata for man, manba, and manteam workflow runs.

  • create <mode> "<task>" starts a record.
  • update <taskId> advances steps or status.
  • review <taskId> … enforces required domains, blockers, and one remediation round.
  • list, show, and clean inspect and maintain records.

mancode manps [area]

Scan

Runs a deterministic preseason health scan. Areas include deps, security, dead-code, and config.

  • Writes Markdown and JSON reports under .mancode/.
  • Scans only; remediation remains an explicit decision.

mancode refresh-style

Rescan

Refreshes the project profile and, when UI assets exist, rescans design tokens.

  • Updates project-profile.json and style-tokens.json.
  • Afterward, run mancode install <platform> --force for static-instruction adapters.

mancode version

Version

Prints the installed CLI version. Use it when reporting an issue or validating an update.

@@ -139,10 +139,10 @@

Workflow records

$ mancode workflow review <taskId> init --review-depth full $ mancode workflow review <taskId> complete --review-domain quality --report film-report-1.md --blockers Q1 $ mancode workflow review <taskId> remediate --resolved Q1 -$ mancode workflow create mamba "verify auth regression" --parent-task <taskId> +$ mancode workflow create manba "verify auth regression" --parent-task <taskId> $ mancode workflow show <taskId> --json $ mancode workflow clean --older-than 30d --dry-run -
Constraint

A linked mamba child may only be created while its parent workflow is active at Step 6. New governed workflows also cannot complete until their required review domains are recorded and all blocker IDs are resolved.

+
Constraint

A linked manba child may only be created while its parent workflow is active at Step 6. New governed workflows also cannot complete until their required review domains are recorded and all blocker IDs are resolved.

diff --git a/website/docs.zh-CN.html b/website/docs.zh-CN.html index 5ba37b8..a0324f9 100644 --- a/website/docs.zh-CN.html +++ b/website/docs.zh-CN.html @@ -23,7 +23,7 @@
mancode - 文档中心 / v0.3.5 + 文档中心 / v0.3.6
@@ -68,25 +68,25 @@

指定平台初始化

如果你知道当前项目要面向哪个 Agen

推荐用法

mancode 的目的不是让所有任务都变重,而是为不同风险选择足够的工程纪律。

-
日常改动

solo

适合小功能、重构或 UI 微调。使用最窄验证和一次 diff 自检,不调用额外 reviewer。

Bug 或不确定行为

/mamba

当正确答案依赖复现与真实验证,而不是一个看起来合理的补丁时使用。

生产或高风险改动

/man

当计划、测试和有界风险审查需要被记录、复查和交接时使用。

团队或维护

/manteam · /manps

团队共享记忆;维护前先执行健康扫描,避免拍脑袋式清理。

+
日常改动

solo

适合小功能、重构或 UI 微调。使用最窄验证和一次 diff 自检,不调用额外 reviewer。

Bug 或不确定行为

/manba

当正确答案依赖复现与真实验证,而不是一个看起来合理的补丁时使用。

生产或高风险改动

/man

当计划、测试和有界风险审查需要被记录、复查和交接时使用。

团队或维护

/manteam · /manps

团队共享记忆;维护前先执行健康扫描,避免拍脑袋式清理。

判断原则

不要因为任务很长就自动使用 /man。当错误代价高、难以回滚,或事后难以审查时,再升级到季后赛模式。

模式

五个工作模式对应不同工程压力。/mansolo 是回到默认 solo 模式的重置命令。

- +
模式适合场景会做什么
solo日常编码轻量 hooks、YAGNI、最窄验证和一次受限 diff 自检。
/mamba诊断复现缺陷、定位根因、驱动真实用户路径并回归验证。
/man高风险改动完整九步工作流、计划关卡、定向/完整风险审查和一轮修复。
/manteam团队项目共享记忆、决策记录、协作和 Conventional Commits 指导。
/manps维护输出 Markdown 与 JSON 的确定性项目健康报告。
solo日常编码轻量 hooks、YAGNI、最窄验证和一次受限 diff 自检。
/manba诊断复现缺陷、定位根因、驱动真实用户路径并回归验证。
/man高风险改动完整九步工作流、计划关卡、定向/完整风险审查和一轮修复。
/manteam团队项目共享记忆、决策记录、协作和 Conventional Commits 指导。
/manps维护输出 Markdown 与 JSON 的确定性项目健康报告。
-

怎样调用

Claude Code 与 Cursor 使用 slash 风格命令;ChatGPT 桌面端 Codex、Codex CLI 和 IDE 从项目 skill 中读取,因此 $man$mamba 等是稳定的显式调用方式。

+

怎样调用

Claude Code 与 Cursor 使用 slash 风格命令;ChatGPT 桌面端 Codex、Codex CLI 和 IDE 从项目 skill 中读取,因此 $man$manba 等是稳定的显式调用方式。

# Claude Code / Cursor
-/mamba
+/manba
 /man
 /manteam
 
 # Codex app / CLI / IDE
-$mamba
+$manba
 $man
-$manteam
+$manteam
@@ -96,7 +96,7 @@

CLI 命令

以下命令除非另有说明,都应该在已初始化

mancode status [--json]

检查

显示当前模式、检测到的技术栈、已安装平台和就绪状态。Claude Code 安装还会显示 hook 注册与预计注入大小。

  • 默认输出用于人工检查。
  • --json 适合脚本或 CI。

mancode install <platform>

适配器

初始化后安装或修复单个平台适配器。

  • 平台:claude-codecursorcodexcopilotzcode
  • --force 重写生成配置。
  • --minimal 只安装 solo 必需项。

mancode list-platforms

发现

列出当前版本可以安装的适配器。自动化选择平台前可以先运行它。

-

mancode workflow …

记录

manmambamanteam 创建和维护受校验的工作流元数据。

  • create <mode> "<task>" 创建记录。
  • update <taskId> 更新步骤或状态。
  • review <taskId> … 约束审查领域、blocker 和唯一一轮修复。
  • listshowclean 用于检查和维护。
+

mancode workflow …

记录

manmanbamanteam 创建和维护受校验的工作流元数据。

  • create <mode> "<task>" 创建记录。
  • update <taskId> 更新步骤或状态。
  • review <taskId> … 约束审查领域、blocker 和唯一一轮修复。
  • listshowclean 用于检查和维护。

mancode manps [area]

扫描

运行确定性的季前赛健康检查。可指定 depssecuritydead-codeconfig

  • 报告写入 .mancode/
  • 默认只扫描,整改仍是明确决策。

mancode refresh-style

刷新

刷新项目 profile;检测到 UI 时重新扫描设计 token。

  • 更新 project-profile.jsonstyle-tokens.json
  • 静态 instruction 适配器需要再运行 mancode install <platform> --force

mancode version

版本

打印当前安装的 CLI 版本。提交 issue 或确认升级时使用。

@@ -108,9 +108,9 @@

CLI 命令

以下命令除非另有说明,都应该在已初始化 $ mancode workflow review <taskId> init --review-depth full $ mancode workflow review <taskId> complete --review-domain quality --report film-report-1.md --blockers Q1 $ mancode workflow review <taskId> remediate --resolved Q1 -$ mancode workflow create mamba "verify auth regression" --parent-task <taskId> +$ mancode workflow create manba "verify auth regression" --parent-task <taskId> $ mancode workflow show <taskId> --json -$ mancode workflow clean --older-than 30d --dry-run

约束

关联的 mamba 子任务只能在父工作流处于 Step 6 且仍在进行时创建。新的严格 workflow 也必须完成所需审查领域并清零 blocker 后才能完成。

+$ mancode workflow clean --older-than 30d --dry-run
约束

关联的 manba 子任务只能在父工作流处于 Step 6 且仍在进行时创建。新的严格 workflow 也必须完成所需审查领域并清零 blocker 后才能完成。

平台与适配器

mancode 不假装所有工具都有相同扩展点。Claude Code 获得最完整的原生能力;其他平台通过持久 rules、skills 或受控 instructions 适配。

平台安装内容说明
Claude CodeHooks、skills、subagents、workflow完整集成:会话启动与提交提示时可注入项目上下文。
Cursor.cursor/rules/*.mdc通过 rules 保存项目指导与模式描述。
CodexAGENTS.md + .agents/skills/在 ChatGPT 桌面端、CLI 与 IDE 使用 $man* 触发 skill。
GitHub Copilot.github/copilot-instructions.md受控 instruction block;当前没有等价 hook 注入。
ZCodeAGENTS.md + 预备项目 skills项目 skill 发现与 slash command 路径仍是发布验证门槛。
diff --git a/website/index.html b/website/index.html index 2f74f55..139c7cb 100644 --- a/website/index.html +++ b/website/index.html @@ -180,8 +180,8 @@

Every task
is not game seven.

- GitHub 上查看 ↗ - +