@givolt/i3-composer-agent 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +2 -2
- package/.claude/commands/i3-build.md +13 -1
- package/.claude/commands/i3-code-simplify.md +13 -1
- package/.claude/commands/i3-constraints.md +13 -1
- package/.claude/commands/i3-plan.md +13 -1
- package/.claude/commands/i3-review.md +13 -1
- package/.claude/commands/i3-ship.md +18 -2
- package/.claude/commands/i3-spec.md +13 -1
- package/.claude/commands/i3-test.md +13 -1
- package/.claude/commands/i3-webperf.md +13 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +7 -6
- package/.gemini/commands/i3-build.toml +13 -1
- package/.gemini/commands/i3-code-simplify.toml +13 -1
- package/.gemini/commands/i3-constraints.toml +13 -1
- package/.gemini/commands/i3-planning.toml +13 -1
- package/.gemini/commands/i3-review.toml +13 -1
- package/.gemini/commands/i3-ship.toml +13 -1
- package/.gemini/commands/i3-spec.toml +13 -1
- package/.gemini/commands/i3-test.toml +13 -1
- package/.gemini/commands/i3-webperf.toml +13 -1
- package/README.md +141 -109
- package/agents/code-reviewer.md +13 -1
- package/agents/security-auditor.md +13 -1
- package/agents/test-engineer.md +13 -1
- package/agents/web-performance-auditor.md +13 -1
- package/bin/asset-fingerprint.cjs +1 -0
- package/bin/i3-composer-agent.cjs +58 -52
- package/bin/install.cjs +42 -19
- package/bin/native-content.cjs +27 -17
- package/bin/tool-menu.cjs +9 -0
- package/bin/tool-selector.cjs +164 -0
- package/bin/wizard.cjs +19 -30
- package/commands/i3-build.toml +13 -1
- package/commands/i3-code-simplify.toml +13 -1
- package/commands/i3-constraints.toml +13 -1
- package/commands/i3-planning.toml +13 -1
- package/commands/i3-review.toml +13 -1
- package/commands/i3-ship.toml +13 -1
- package/commands/i3-spec.toml +13 -1
- package/commands/i3-test.toml +13 -1
- package/commands/i3-webperf.toml +13 -1
- package/docs/adoption-guide.md +8 -0
- package/docs/agents.md +8 -0
- package/docs/antigravity-setup.md +8 -0
- package/docs/codebuddy-setup.md +31 -62
- package/docs/codex-setup.md +18 -29
- package/docs/commandcode-setup.md +8 -0
- package/docs/copilot-cli-setup.md +8 -0
- package/docs/copilot-setup.md +8 -0
- package/docs/cursor-setup.md +8 -0
- package/docs/gemini-cli-setup.md +8 -0
- package/docs/getting-started.md +8 -0
- package/docs/opencode-setup.md +8 -0
- package/docs/other-hosts.md +8 -0
- package/docs/windsurf-setup.md +8 -0
- package/docs/workbuddy-setup.md +41 -65
- package/evals/README.md +86 -0
- package/evals/cases/i3-api-and-interface-design.json +45 -0
- package/evals/cases/i3-browser-testing-with-devtools.json +44 -0
- package/evals/cases/i3-ci-cd-and-automation.json +45 -0
- package/evals/cases/i3-code-review-and-quality.json +49 -0
- package/evals/cases/i3-code-simplification.json +45 -0
- package/evals/cases/i3-constraint-driven-development.json +79 -0
- package/evals/cases/i3-context-engineering.json +44 -0
- package/evals/cases/i3-debugging-and-error-recovery.json +58 -0
- package/evals/cases/i3-deprecation-and-migration.json +52 -0
- package/evals/cases/i3-documentation-and-adrs.json +44 -0
- package/evals/cases/i3-doubt-driven-development.json +43 -0
- package/evals/cases/i3-frontend-ui-engineering.json +52 -0
- package/evals/cases/i3-git-workflow-and-versioning.json +47 -0
- package/evals/cases/i3-idea-refine.json +43 -0
- package/evals/cases/i3-incremental-implementation.json +57 -0
- package/evals/cases/i3-interview-me.json +42 -0
- package/evals/cases/i3-observability-and-instrumentation.json +44 -0
- package/evals/cases/i3-performance-optimization.json +53 -0
- package/evals/cases/i3-planning-and-task-breakdown.json +45 -0
- package/evals/cases/i3-security-and-hardening.json +53 -0
- package/evals/cases/i3-shipping-and-launch.json +56 -0
- package/evals/cases/i3-source-driven-development.json +44 -0
- package/evals/cases/i3-spec-driven-development.json +72 -0
- package/evals/cases/i3-test-driven-development.json +73 -0
- package/evals/cases/i3-using-agent-skills.json +44 -0
- package/evals/fixtures/i3-api-and-interface-design/service-brief.md +19 -0
- package/evals/fixtures/i3-browser-testing-with-devtools/README.md +5 -0
- package/evals/fixtures/i3-browser-testing-with-devtools/index.html +24 -0
- package/evals/fixtures/i3-browser-testing-with-devtools/server.js +15 -0
- package/evals/fixtures/i3-ci-cd-and-automation/package.json +8 -0
- package/evals/fixtures/i3-ci-cd-and-automation/src/slug.js +3 -0
- package/evals/fixtures/i3-ci-cd-and-automation/test/slug.test.js +9 -0
- package/evals/fixtures/i3-code-review-and-quality/user-search.diff +16 -0
- package/evals/fixtures/i3-code-simplification/config-parser.js +46 -0
- package/evals/fixtures/i3-code-simplification/config-parser.test.js +15 -0
- package/evals/fixtures/i3-context-engineering/context-audit.md +15 -0
- package/evals/fixtures/i3-debugging-and-error-recovery/pagination.js +8 -0
- package/evals/fixtures/i3-debugging-and-error-recovery/pagination.test.js +9 -0
- package/evals/fixtures/i3-debugging-and-error-recovery/time-pressure.md +6 -0
- package/evals/fixtures/i3-deprecation-and-migration/api-inventory.md +9 -0
- package/evals/fixtures/i3-documentation-and-adrs/decision-context.md +16 -0
- package/evals/fixtures/i3-doubt-driven-development/migration-plan.md +19 -0
- package/evals/fixtures/i3-frontend-ui-engineering/Button.tsx +7 -0
- package/evals/fixtures/i3-frontend-ui-engineering/design-system.md +11 -0
- package/evals/fixtures/i3-git-workflow-and-versioning/.eval/working-tree.patch +21 -0
- package/evals/fixtures/i3-git-workflow-and-versioning/app.js +7 -0
- package/evals/fixtures/i3-git-workflow-and-versioning/app.test.js +9 -0
- package/evals/fixtures/i3-incremental-implementation/reports.js +7 -0
- package/evals/fixtures/i3-incremental-implementation/reports.test.js +12 -0
- package/evals/fixtures/i3-incremental-implementation/tasks/plan.md +8 -0
- package/evals/fixtures/i3-observability-and-instrumentation/operations.md +11 -0
- package/evals/fixtures/i3-observability-and-instrumentation/payment-retry.js +14 -0
- package/evals/fixtures/i3-performance-optimization/benchmark.js +15 -0
- package/evals/fixtures/i3-performance-optimization/products.js +14 -0
- package/evals/fixtures/i3-planning-and-task-breakdown/notifications-spec.md +18 -0
- package/evals/fixtures/i3-security-and-hardening/webhook.js +11 -0
- package/evals/fixtures/i3-security-and-hardening/webhook.test.js +13 -0
- package/evals/fixtures/i3-shipping-and-launch/authority-pressure.md +6 -0
- package/evals/fixtures/i3-shipping-and-launch/launch-status.md +11 -0
- package/evals/fixtures/i3-source-driven-development/framework-task.md +10 -0
- package/evals/fixtures/i3-spec-driven-development/billing-brief.md +16 -0
- package/evals/fixtures/i3-test-driven-development/BUG.md +10 -0
- package/evals/fixtures/i3-test-driven-development/README.md +28 -0
- package/evals/fixtures/i3-test-driven-development/package.json +9 -0
- package/evals/fixtures/i3-test-driven-development/src/split.js +8 -0
- package/evals/fixtures/i3-test-driven-development/test/split.test.js +13 -0
- package/evals/fixtures/i3-using-agent-skills/incident.md +6 -0
- package/evals/fixtures/incremental-implementation-pressure/draft-export.js +17 -0
- package/evals/fixtures/incremental-implementation-pressure/scenario.md +9 -0
- package/evals/fixtures/spec-driven-development-decomposition/portal-brief.md +22 -0
- package/evals/fixtures/test-driven-development-ecosystem/README.md +9 -0
- package/evals/fixtures/test-driven-development-ecosystem/ledger.py +15 -0
- package/evals/fixtures/test-driven-development-ecosystem/test_ledger.py +19 -0
- package/evals/plugin/code-review-fires/graders/off-by-one.md +7 -0
- package/evals/plugin/code-review-fires/graders/severity-labels.md +4 -0
- package/evals/plugin/code-review-fires/graders/skill-fired.md +5 -0
- package/evals/plugin/code-review-fires/prompt.md +31 -0
- package/evals/plugin/code-review-stays-quiet/graders/not-fired.md +8 -0
- package/evals/plugin/code-review-stays-quiet/graders/tdd-fired.md +6 -0
- package/evals/plugin/code-review-stays-quiet/prompt.md +8 -0
- package/evals/plugin/code-review-stays-quiet-on-commit-message/graders/not-fired.md +8 -0
- package/evals/plugin/code-review-stays-quiet-on-commit-message/prompt.md +25 -0
- package/evals/zh-CN/cases/i3-api-and-interface-design.json +46 -0
- package/evals/zh-CN/cases/i3-browser-testing-with-devtools.json +45 -0
- package/evals/zh-CN/cases/i3-ci-cd-and-automation.json +46 -0
- package/evals/zh-CN/cases/i3-code-review-and-quality.json +46 -0
- package/evals/zh-CN/cases/i3-code-simplification.json +46 -0
- package/evals/zh-CN/cases/i3-constraint-driven-development.json +70 -0
- package/evals/zh-CN/cases/i3-context-engineering.json +45 -0
- package/evals/zh-CN/cases/i3-debugging-and-error-recovery.json +60 -0
- package/evals/zh-CN/cases/i3-deprecation-and-migration.json +45 -0
- package/evals/zh-CN/cases/i3-documentation-and-adrs.json +45 -0
- package/evals/zh-CN/cases/i3-doubt-driven-development.json +45 -0
- package/evals/zh-CN/cases/i3-frontend-ui-engineering.json +45 -0
- package/evals/zh-CN/cases/i3-git-workflow-and-versioning.json +45 -0
- package/evals/zh-CN/cases/i3-idea-refine.json +44 -0
- package/evals/zh-CN/cases/i3-incremental-implementation.json +59 -0
- package/evals/zh-CN/cases/i3-interview-me.json +43 -0
- package/evals/zh-CN/cases/i3-observability-and-instrumentation.json +46 -0
- package/evals/zh-CN/cases/i3-performance-optimization.json +46 -0
- package/evals/zh-CN/cases/i3-planning-and-task-breakdown.json +46 -0
- package/evals/zh-CN/cases/i3-security-and-hardening.json +46 -0
- package/evals/zh-CN/cases/i3-shipping-and-launch.json +59 -0
- package/evals/zh-CN/cases/i3-source-driven-development.json +45 -0
- package/evals/zh-CN/cases/i3-spec-driven-development.json +62 -0
- package/evals/zh-CN/cases/i3-test-driven-development.json +76 -0
- package/evals/zh-CN/cases/i3-using-agent-skills.json +45 -0
- package/hooks/SDD-CACHE.md +8 -0
- package/hooks/SIMPLIFY-IGNORE.md +8 -0
- package/hooks/sdd-cache-pre.sh +5 -5
- package/hooks/session-start-test.sh +1 -1
- package/hooks/session-start.sh +3 -3
- package/hooks/simplify-ignore.sh +8 -8
- package/package.json +235 -6
- package/plugin.json +2 -2
- package/references/accessibility-checklist.md +8 -0
- package/references/definition-of-done.md +8 -0
- package/references/observability-checklist.md +8 -0
- package/references/orchestration-patterns.md +8 -0
- package/references/performance-checklist.md +8 -0
- package/references/security-checklist.md +8 -0
- package/references/testing-patterns.md +8 -0
- package/scripts/lib/skill-lint.js +586 -0
- package/scripts/run-evals.js +659 -0
- package/scripts/validate-artifact-paths.js +130 -0
- package/scripts/validate-commands.js +224 -0
- package/scripts/validate-reference-links.js +187 -0
- package/scripts/validate-skills.js +70 -0
- package/scripts/validate-versions.js +35 -0
- package/skills/i3-api-and-interface-design/SKILL.md +13 -1
- package/skills/i3-browser-testing-with-devtools/SKILL.md +13 -1
- package/skills/i3-ci-cd-and-automation/SKILL.md +13 -1
- package/skills/i3-code-review-and-quality/SKILL.md +13 -1
- package/skills/i3-code-simplification/SKILL.md +13 -1
- package/skills/i3-constraint-driven-development/SKILL.md +13 -1
- package/skills/i3-constraint-driven-development/references/floor-guard.md +8 -0
- package/skills/i3-context-engineering/SKILL.md +13 -1
- package/skills/i3-debugging-and-error-recovery/SKILL.md +13 -1
- package/skills/i3-deprecation-and-migration/SKILL.md +13 -1
- package/skills/i3-documentation-and-adrs/SKILL.md +13 -1
- package/skills/i3-doubt-driven-development/SKILL.md +13 -1
- package/skills/i3-frontend-ui-engineering/SKILL.md +13 -1
- package/skills/i3-git-workflow-and-versioning/SKILL.md +13 -1
- package/skills/i3-idea-refine/SKILL.md +13 -1
- package/skills/i3-idea-refine/examples.md +8 -0
- package/skills/i3-idea-refine/frameworks.md +8 -0
- package/skills/i3-idea-refine/refinement-criteria.md +8 -0
- package/skills/i3-idea-refine/scripts/idea-refine.sh +2 -2
- package/skills/i3-incremental-implementation/SKILL.md +13 -1
- package/skills/i3-interview-me/SKILL.md +13 -1
- package/skills/i3-observability-and-instrumentation/SKILL.md +13 -1
- package/skills/i3-performance-optimization/SKILL.md +13 -1
- package/skills/i3-performance-optimization/references/optimization-patterns.md +8 -0
- package/skills/i3-planning-and-task-breakdown/SKILL.md +13 -1
- package/skills/i3-security-and-hardening/SKILL.md +13 -1
- package/skills/i3-security-and-hardening/references/hardening-patterns.md +8 -0
- package/skills/i3-shipping-and-launch/SKILL.md +13 -1
- package/skills/i3-source-driven-development/SKILL.md +13 -1
- package/skills/i3-spec-driven-development/SKILL.md +13 -1
- package/skills/i3-test-driven-development/SKILL.md +13 -1
- package/skills/i3-using-agent-skills/SKILL.md +13 -1
|
@@ -13,9 +13,9 @@ const identity = `i3 Composer Agent v${pkg.version}`;
|
|
|
13
13
|
const help = `${identity}
|
|
14
14
|
${pkg.name}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
通过多工具安装向导安装软件工程技能,默认中文回复。
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
用法:
|
|
19
19
|
npx ${pkg.name}
|
|
20
20
|
npx ${pkg.name} --codebuddy
|
|
21
21
|
npx ${pkg.name} --workbuddy --scope global
|
|
@@ -24,28 +24,32 @@ Usage:
|
|
|
24
24
|
npx ${pkg.name} --prepare --claude --dir <directory>
|
|
25
25
|
npx ${pkg.name} --extract --dir <directory>
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
--agents <ids>
|
|
29
|
-
--codebuddy
|
|
30
|
-
--workbuddy
|
|
31
|
-
--codex
|
|
32
|
-
--claude
|
|
33
|
-
--scope <scope> project
|
|
34
|
-
--project-dir <dir>
|
|
35
|
-
--yes
|
|
36
|
-
--list-agents
|
|
37
|
-
--prepare
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
27
|
+
选项:
|
|
28
|
+
--agents <ids> 工具 ID,逗号分隔,可同时选择多个
|
|
29
|
+
--codebuddy 预选 CodeBuddy 桌面 IDE / CodeBuddy Code
|
|
30
|
+
--workbuddy 预选 WorkBuddy(桌面版建议全局安装)
|
|
31
|
+
--codex 预选 Codex
|
|
32
|
+
--claude 预选 Claude Code
|
|
33
|
+
--scope <scope> project 项目 / global 全局(也可 --global / --local)
|
|
34
|
+
--project-dir <dir> 项目安装根目录,默认当前目录
|
|
35
|
+
--yes 确认明确指定的非交互安装
|
|
36
|
+
--list-agents 列出全部支持的工具目录
|
|
37
|
+
--prepare 另行导出插件目录并显示宿主注册步骤
|
|
38
|
+
--extract 导出完整插件内容
|
|
39
|
+
--dir <directory> 插件输出目录;隐含 --prepare
|
|
40
|
+
--dry-run 显示安装/导出计划,不写入文件
|
|
41
|
+
--force 确认替换已有内容,成功后清理旧文件
|
|
42
|
+
--version 输出包版本
|
|
43
|
+
--help 显示帮助
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
WorkBuddy
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
终端向导先显示常用工具,WorkBuddy 在第 2 项;选择“更多”可分页/搜索全部工具。
|
|
46
|
+
上下键移动/跨页,空格勾选/取消,回车确认;列表下实时显示已选,q/Esc 退出。
|
|
47
|
+
CodeBuddy、WorkBuddy、Claude Code 同时安装短命令和 Agent 角色。
|
|
48
|
+
其他工具通过 Skills 调用,短命令支持取决于宿主。
|
|
49
|
+
所有工具默认安装完整配套目录 i3-composer-agent/,含 evals、hooks、scripts 及所需资料。
|
|
50
|
+
WorkBuddy 桌面版建议全局安装,便于技能列表和输入补全发现。
|
|
51
|
+
评测需主动运行;Hook 文件已提供,需按宿主事件协议单独启用。
|
|
52
|
+
--prepare 仅供插件管理器安装方式使用,不自动注册插件或启用 Hook。
|
|
49
53
|
`;
|
|
50
54
|
|
|
51
55
|
function quote(value) { return "'" + value.replaceAll("'", "'\\''") + "'"; }
|
|
@@ -55,7 +59,7 @@ function realDestination(target) {
|
|
|
55
59
|
while (!fs.existsSync(existing)) {
|
|
56
60
|
suffix.unshift(path.basename(existing));
|
|
57
61
|
const parent = path.dirname(existing);
|
|
58
|
-
if (parent === existing) throw new Error(
|
|
62
|
+
if (parent === existing) throw new Error(`无法解析输出目录: ${target}`);
|
|
59
63
|
existing = parent;
|
|
60
64
|
}
|
|
61
65
|
return path.join(fs.realpathSync(existing), ...suffix);
|
|
@@ -68,29 +72,29 @@ function prepare() {
|
|
|
68
72
|
for (let i = 0; i < args.length; i++) {
|
|
69
73
|
const arg = args[i];
|
|
70
74
|
if (arg === '--prepare') continue;
|
|
71
|
-
if (arg === '--workbuddy') throw new Error('
|
|
75
|
+
if (arg === '--workbuddy') throw new Error('安装 WorkBuddy 原生技能请使用 --workbuddy --scope project|global --yes,移除 --prepare 和 --dir。');
|
|
72
76
|
if (['--codebuddy', '--codex', '--claude', '--extract'].includes(arg)) {
|
|
73
|
-
if (host) throw new Error('
|
|
77
|
+
if (host) throw new Error('请选择一个宿主参数或 --extract。');
|
|
74
78
|
host = arg.slice(2);
|
|
75
79
|
} else if (arg === '--dir') {
|
|
76
|
-
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error('--dir
|
|
80
|
+
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error('--dir 需要指定目录。');
|
|
77
81
|
directory = path.resolve(args[++i]);
|
|
78
82
|
} else if (arg === '--dry-run') dryRun = true;
|
|
79
83
|
else if (arg === '--force') force = true;
|
|
80
|
-
else throw new Error(
|
|
84
|
+
else throw new Error(`未知选项: ${arg}`);
|
|
81
85
|
}
|
|
82
|
-
if (!host) throw new Error('
|
|
86
|
+
if (!host) throw new Error('请选择 --codebuddy、--codex、--claude 或 --extract。');
|
|
83
87
|
directory = realDestination(directory);
|
|
84
88
|
const sourceRoot = fs.realpathSync(root);
|
|
85
89
|
if (directory === sourceRoot || directory.startsWith(sourceRoot + path.sep) || sourceRoot.startsWith(directory + path.sep)) {
|
|
86
|
-
throw new Error('
|
|
90
|
+
throw new Error('输出目录必须位于包目录之外。');
|
|
87
91
|
}
|
|
88
92
|
let exists = false;
|
|
89
93
|
try { fs.lstatSync(directory); exists = true; } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
90
94
|
if (exists) {
|
|
91
|
-
if (!force) throw new Error(
|
|
95
|
+
if (!force) throw new Error(`输出目录已存在: ${directory}. 请选择新目录,或对已有 i3 插件使用 --force。`);
|
|
92
96
|
const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'plugin.json'), 'utf8'));
|
|
93
|
-
if (manifest.name !== name) throw new Error('
|
|
97
|
+
if (manifest.name !== name) throw new Error('不能替换属于其他插件的目录。');
|
|
94
98
|
}
|
|
95
99
|
if (dryRun) {
|
|
96
100
|
console.log(JSON.stringify({ name, version: pkg.version, directory, host, files: pkg.i3ComposerAgent.pluginFiles, replacesExisting: exists }, null, 2));
|
|
@@ -109,7 +113,7 @@ function prepare() {
|
|
|
109
113
|
recursive: true,
|
|
110
114
|
filter: file => {
|
|
111
115
|
if (path.basename(file) === '.DS_Store') return false;
|
|
112
|
-
if (fs.lstatSync(file).isSymbolicLink()) throw new Error(
|
|
116
|
+
if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`插件内容中包含符号链接: ${file}`);
|
|
113
117
|
return !/(?:^|[\\/])(?:results|node_modules|\.git)(?:[\\/]|$)/.test(path.relative(root, file));
|
|
114
118
|
},
|
|
115
119
|
});
|
|
@@ -131,18 +135,19 @@ function prepare() {
|
|
|
131
135
|
});
|
|
132
136
|
}
|
|
133
137
|
console.log(identity);
|
|
134
|
-
console.log(
|
|
138
|
+
console.log(`已在 ${directory} 准备好 ${name}`);
|
|
139
|
+
console.log('包含 evals、hooks、scripts 及依赖资料;评测需主动运行,Hook 需按宿主事件协议启用。');
|
|
135
140
|
if (host === 'extract') return;
|
|
136
|
-
console.log('\
|
|
141
|
+
console.log('\n请在目标宿主中执行以下原生命令注册并启用插件:');
|
|
137
142
|
const selector = `${name}@${name}`;
|
|
138
143
|
if (host === 'codex') {
|
|
139
144
|
console.log(`codex plugin marketplace add ${quote(directory)}`);
|
|
140
145
|
console.log(`codex plugin add ${selector}`);
|
|
141
|
-
console.log('
|
|
146
|
+
console.log('请新建 Codex 会话,通过 $i3- 开头的技能名称调用;生命周期短命令由宿主适配决定。');
|
|
142
147
|
} else {
|
|
143
148
|
console.log(`/plugin marketplace add ${quote(directory)}`);
|
|
144
149
|
console.log(`/plugin install ${selector}`);
|
|
145
|
-
console.log(
|
|
150
|
+
console.log(`在目标宿主使用 /${name}:i3-spec、/${name}:i3-plan、/${name}:i3-build 和 /${name}:i3-ship: ${host === 'claude' ? 'Claude Code' : 'CodeBuddy'}.`);
|
|
146
151
|
}
|
|
147
152
|
}
|
|
148
153
|
async function main() {
|
|
@@ -151,7 +156,7 @@ async function main() {
|
|
|
151
156
|
if (args.some(arg => ['--extract', '--prepare', '--dir'].includes(arg))) { prepare(); return; }
|
|
152
157
|
const installer = require('./install.cjs');
|
|
153
158
|
if (args.length === 1 && args[0] === '--list-agents') {
|
|
154
|
-
for (const agent of
|
|
159
|
+
for (const agent of require('./tool-menu.cjs').orderedAgents) console.log(`${agent.id.padEnd(20)} ${agent.label}${agent.globalSkillsDir ? '' : '(仅项目安装)'}`);
|
|
155
160
|
return;
|
|
156
161
|
}
|
|
157
162
|
const options = { agents: [], projectDir: process.cwd() };
|
|
@@ -159,27 +164,27 @@ async function main() {
|
|
|
159
164
|
const arg = args[i];
|
|
160
165
|
if (['--codebuddy', '--workbuddy', '--codex', '--claude'].includes(arg)) options.agents.push(arg.slice(2));
|
|
161
166
|
else if (['--agents', '--scope', '--project-dir'].includes(arg)) {
|
|
162
|
-
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error(`${arg}
|
|
167
|
+
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error(`${arg} 需要指定值。`);
|
|
163
168
|
const value = args[++i];
|
|
164
169
|
if (arg === '--agents') options.agents.push(value);
|
|
165
|
-
else if (arg === '--scope') { if (options.scope && options.scope !== value) throw new Error('
|
|
170
|
+
else if (arg === '--scope') { if (options.scope && options.scope !== value) throw new Error('请选择一个安装范围。'); options.scope = value; }
|
|
166
171
|
else options.projectDir = path.resolve(value);
|
|
167
172
|
} else if (arg === '--global' || arg === '--local') {
|
|
168
173
|
const scope = arg === '--global' ? 'global' : 'project';
|
|
169
|
-
if (options.scope && options.scope !== scope) throw new Error('
|
|
174
|
+
if (options.scope && options.scope !== scope) throw new Error('请选择一个安装范围。');
|
|
170
175
|
options.scope = scope;
|
|
171
176
|
} else if (arg === '--yes') options.yes = true;
|
|
172
177
|
else if (arg === '--force') options.force = true;
|
|
173
178
|
else if (arg === '--dry-run') options.dryRun = true;
|
|
174
|
-
else throw new Error(
|
|
179
|
+
else throw new Error(`未知选项: ${arg}`);
|
|
175
180
|
}
|
|
176
181
|
options.agents = installer.selectAgents(options.agents);
|
|
177
182
|
if (process.stdin.isTTY && process.stdout.isTTY && !options.yes && !options.dryRun) {
|
|
178
183
|
const selection = await require('./wizard.cjs').choose(options, identity);
|
|
179
|
-
if (!selection) { console.log('
|
|
184
|
+
if (!selection) { console.log('安装已取消,未修改文件。'); return; }
|
|
180
185
|
Object.assign(options, selection);
|
|
181
186
|
} else if (!options.agents.length || !options.scope || (!options.yes && !options.dryRun)) {
|
|
182
|
-
throw new Error('
|
|
187
|
+
throw new Error('请在交互终端使用向导,或明确指定 --agents <ids> --scope project|global --yes。');
|
|
183
188
|
}
|
|
184
189
|
const plan = installer.planInstallation(options);
|
|
185
190
|
if (options.dryRun) {
|
|
@@ -188,19 +193,20 @@ async function main() {
|
|
|
188
193
|
}
|
|
189
194
|
if (!options.yes) {
|
|
190
195
|
const confirmed = await require('./wizard.cjs').confirm(plan);
|
|
191
|
-
if (!confirmed) { console.log('
|
|
196
|
+
if (!confirmed) { console.log('安装已取消,未修改文件。'); return; }
|
|
192
197
|
options.force = plan.conflicts.length > 0;
|
|
193
198
|
}
|
|
194
199
|
installer.executeInstallation(plan, { force: options.force });
|
|
195
200
|
console.log(identity);
|
|
196
|
-
if (plan.reused.length) console.log(
|
|
201
|
+
if (plan.reused.length) console.log(`已复用同版本的 ${plan.reused.length} 个未修改内容。`);
|
|
197
202
|
for (const target of plan.targets) {
|
|
198
|
-
console.log(
|
|
199
|
-
if (target.commandsDirectory) console.log(
|
|
203
|
+
console.log(`已为 ${target.agents.join(', ')} 安装技能:${target.skillsDirectory}`);
|
|
204
|
+
if (target.commandsDirectory) console.log(`命令:/i3-spec、/i3-plan、/i3-build、/i3-ship(共 ${fs.readdirSync(target.commandsDirectory).filter(file => file.endsWith('.md')).length} 个工作流):${target.commandsDirectory}`);
|
|
205
|
+
console.log(`配套目录(含 evals、hooks、scripts):${target.supportDirectory}`);
|
|
206
|
+
console.log(`中文评测:node "${path.join(target.supportDirectory, 'scripts/run-evals.js')}" --locale zh-CN --min-rank1 95`);
|
|
200
207
|
}
|
|
201
|
-
console.log('
|
|
202
|
-
if (plan.agents.some(id => !['codebuddy', 'workbuddy', 'claude-code'].includes(id))) console.log('
|
|
203
|
-
console.log('
|
|
204
|
-
if (plan.agents.some(id => ['codebuddy', 'codex', 'claude-code'].includes(id))) console.log('For CodeBuddy, Codex or Claude Code plugin registration, use --prepare with the matching host flag.');
|
|
208
|
+
console.log('请重新加载 IDE 窗口或新建宿主会话以发现安装内容。');
|
|
209
|
+
if (plan.agents.some(id => !['codebuddy', 'workbuddy', 'claude-code'].includes(id))) console.log('这些工具通过技能 ID 调用;生命周期短命令支持取决于宿主。');
|
|
210
|
+
console.log('安装完成。评测运行方式见配套目录 evals/README.md;Hook 文件已安装,尚未启用。');
|
|
205
211
|
}
|
|
206
212
|
main().catch(error => { console.error(`${name}: ${error.message}`); process.exitCode = 1; });
|
package/bin/install.cjs
CHANGED
|
@@ -11,19 +11,20 @@ const { fingerprint } = require('./asset-fingerprint.cjs');
|
|
|
11
11
|
const root = path.resolve(__dirname, '..');
|
|
12
12
|
const name = 'i3-composer-agent';
|
|
13
13
|
const version = require('../package.json').version;
|
|
14
|
+
const pluginFiles = require('../package.json').i3ComposerAgent.pluginFiles;
|
|
14
15
|
const aliases = { claude: 'claude-code', gemini: 'gemini-cli', copilot: 'github-copilot' };
|
|
15
16
|
function exists(file) {
|
|
16
17
|
try { fs.lstatSync(file); return true; }
|
|
17
18
|
catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
18
19
|
}
|
|
19
20
|
function selectAgents(values) {
|
|
20
|
-
const ids = [...new Set(values.flatMap(value => value.split(
|
|
21
|
-
for (const id of ids) if (!registry.agents.some(agent => agent.id === id)) throw new Error(
|
|
21
|
+
const ids = [...new Set(values.flatMap(value => value.split(/[,,]/)).map(id => aliases[id.trim()] || id.trim()).filter(Boolean))];
|
|
22
|
+
for (const id of ids) if (!registry.agents.some(agent => agent.id === id)) throw new Error(`未知工具:${id}。请使用 --list-agents 查看支持列表。`);
|
|
22
23
|
return ids;
|
|
23
24
|
}
|
|
24
25
|
function globalSkillsDir(agent, home, env) {
|
|
25
26
|
const config = agent.globalSkillsDir;
|
|
26
|
-
if (!config) throw new Error(`${agent.label}
|
|
27
|
+
if (!config) throw new Error(`${agent.label} 仅支持项目安装(project installation only),请选择项目范围或移除此工具。`);
|
|
27
28
|
let base = home;
|
|
28
29
|
if (config.base === 'config') base = env.XDG_CONFIG_HOME && path.isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : path.join(home, '.config');
|
|
29
30
|
if (config.base === 'env') base = env[config.env]?.trim() || path.join(home, config.fallback);
|
|
@@ -43,23 +44,24 @@ function destination(file) {
|
|
|
43
44
|
function checkDestination(file) {
|
|
44
45
|
const source = fs.realpathSync(root);
|
|
45
46
|
if (file === source || file.startsWith(source + path.sep) || source.startsWith(file + path.sep)) {
|
|
46
|
-
throw new Error('
|
|
47
|
+
throw new Error('安装目标必须位于包目录之外。');
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
function assetPath(base, file) { return path.relative(base, file).split(path.sep).join('/'); }
|
|
49
51
|
function manifestContent(plan, asset) {
|
|
50
52
|
return JSON.stringify({ name, version, entryPrefix: 'i3-', scope: plan.scope, agents: asset.target.agents,
|
|
51
|
-
assets: plan.assets.filter(item => item.base === asset.base && item.kind !== 'legacy').map(item =>
|
|
53
|
+
assets: plan.assets.filter(item => item.base === asset.base && item.kind !== 'legacy').map(item => assetPath(asset.base, item.destination)) }, null, 2) + '\n';
|
|
52
54
|
}
|
|
53
55
|
function planInstallation({ agents, scope, projectDir = process.cwd(), home = os.homedir(), env = process.env }) {
|
|
54
56
|
agents = selectAgents(agents);
|
|
55
|
-
if (!agents.length) throw new Error('
|
|
56
|
-
if (!['project', 'global'].includes(scope)) throw new Error('
|
|
57
|
+
if (!agents.length) throw new Error('请至少选择一个工具。');
|
|
58
|
+
if (!['project', 'global'].includes(scope)) throw new Error('请选择 --scope project 或 --scope global。');
|
|
57
59
|
const targets = new Map();
|
|
58
60
|
for (const id of agents) {
|
|
59
61
|
const agent = registry.agents.find(item => item.id === id);
|
|
60
62
|
const skillsDirectory = destination(scope === 'project' ? path.resolve(projectDir, agent.projectSkillsDir) : globalSkillsDir(agent, home, env));
|
|
61
63
|
const base = path.dirname(skillsDirectory);
|
|
62
|
-
if (!targets.has(skillsDirectory)) targets.set(skillsDirectory, { agents: [], skillsDirectory, referencesDirectory: path.join(base, 'references'), base });
|
|
64
|
+
if (!targets.has(skillsDirectory)) targets.set(skillsDirectory, { agents: [], skillsDirectory, referencesDirectory: path.join(base, 'references'), supportDirectory: path.join(base, name), base });
|
|
63
65
|
const target = targets.get(skillsDirectory);
|
|
64
66
|
target.agents.push(id);
|
|
65
67
|
if (['codebuddy', 'workbuddy', 'claude-code'].includes(id)) {
|
|
@@ -70,7 +72,7 @@ function planInstallation({ agents, scope, projectDir = process.cwd(), home = os
|
|
|
70
72
|
const assets = [];
|
|
71
73
|
function addDirectory(sourceDirectory, destinationDirectory, kind) {
|
|
72
74
|
if (exists(destinationDirectory) && fs.lstatSync(destinationDirectory).isSymbolicLink()) {
|
|
73
|
-
throw new Error(
|
|
75
|
+
throw new Error(`安装目录是符号链接(symlink): ${destinationDirectory}. 请选择其真实目录。`);
|
|
74
76
|
}
|
|
75
77
|
for (const entry of fs.readdirSync(path.join(root, sourceDirectory)).sort()) {
|
|
76
78
|
if (entry === '.DS_Store') continue;
|
|
@@ -82,6 +84,9 @@ function planInstallation({ agents, scope, projectDir = process.cwd(), home = os
|
|
|
82
84
|
const previous = exists(manifestFile) && !fs.lstatSync(manifestFile).isSymbolicLink()
|
|
83
85
|
? JSON.parse(fs.readFileSync(manifestFile, 'utf8')) : undefined;
|
|
84
86
|
const owned = previous?.name === name && Array.isArray(previous.assets);
|
|
87
|
+
// Earlier Windows installs persisted backslashes. Normalize before lookup
|
|
88
|
+
// and containment checks so owned upgrades work across both manifest forms.
|
|
89
|
+
if (owned) previous.assets = previous.assets.map(relative => typeof relative === 'string' ? relative.replaceAll('\\', '/') : relative);
|
|
85
90
|
const sameVersion = owned && previous.version === version && previous.scope === scope && previous.entryPrefix === 'i3-';
|
|
86
91
|
if (owned && previous.scope === scope && Array.isArray(previous.agents)) {
|
|
87
92
|
const applicable = previous.agents.filter(id => {
|
|
@@ -99,17 +104,34 @@ function planInstallation({ agents, scope, projectDir = process.cwd(), home = os
|
|
|
99
104
|
addDirectory('.claude/commands', target.commandsDirectory, 'command');
|
|
100
105
|
addDirectory('agents', target.personasDirectory, 'persona');
|
|
101
106
|
const docs = path.join(target.base, 'docs');
|
|
102
|
-
if (exists(docs) && fs.lstatSync(docs).isSymbolicLink()) throw new Error(
|
|
107
|
+
if (exists(docs) && fs.lstatSync(docs).isSymbolicLink()) throw new Error(`安装目录是符号链接(symlink): ${docs}.`);
|
|
103
108
|
assets.push({ source: path.join(root, 'docs/agents.md'), destination: destination(path.join(target.base, 'docs/agents.md')), kind: 'documentation' });
|
|
104
109
|
}
|
|
105
110
|
const licenseDirectory = path.join(target.referencesDirectory, name);
|
|
106
|
-
if (exists(licenseDirectory) && fs.lstatSync(licenseDirectory).isSymbolicLink()) throw new Error(
|
|
111
|
+
if (exists(licenseDirectory) && fs.lstatSync(licenseDirectory).isSymbolicLink()) throw new Error(`安装目录是符号链接(symlink): ${licenseDirectory}.`);
|
|
107
112
|
assets.push({ source: path.join(root, 'LICENSE'), destination: destination(path.join(licenseDirectory, 'LICENSE')), kind: 'attribution' });
|
|
113
|
+
// Keep the canonical plugin layout for eval fixtures, validators and Hook
|
|
114
|
+
// relative paths. Native entry points above remain host-adapted copies.
|
|
115
|
+
for (const relative of pluginFiles) {
|
|
116
|
+
const file = path.join(target.supportDirectory, relative);
|
|
117
|
+
if (destination(file) !== file) throw new Error(`配套目录包含符号链接(symlink): ${file}`);
|
|
118
|
+
assets.push({ source: path.join(root, relative), destination: file, kind: 'support' });
|
|
119
|
+
}
|
|
108
120
|
if (owned) {
|
|
109
121
|
const legacy = fs.readdirSync(path.join(root, 'skills')).filter(id => id.startsWith('i3-')).map(id => `skills/${id.slice(3)}`);
|
|
110
122
|
if (target.commandsDirectory) legacy.push(...fs.readdirSync(path.join(root, '.claude/commands')).filter(file => file.startsWith('i3-') && file.endsWith('.md')).map(file => `commands/${file.slice(3)}`));
|
|
111
123
|
legacy.push(`${name}-LICENSE`, `${name}-NOTICE.md`);
|
|
112
|
-
|
|
124
|
+
const current = new Set(assets.slice(start).map(asset => assetPath(target.base, asset.destination)));
|
|
125
|
+
for (const relative of previous.assets) {
|
|
126
|
+
// Retire only owned native entries or entries inside this package's
|
|
127
|
+
// companion directory. Never let a manifest extend the deletion scope.
|
|
128
|
+
if (typeof relative !== 'string' || relative.includes('\0') || relative.includes(':')
|
|
129
|
+
|| relative.split('/').some(part => ['', '.', '..'].includes(part))) continue;
|
|
130
|
+
if (!/^(skills|commands|agents|references)\/[^/]+$/.test(relative) && !relative.startsWith(`${name}/`)) continue;
|
|
131
|
+
if (relative.startsWith(`${name}/`) && destination(path.join(target.base, relative)) !== path.join(target.base, relative)) continue;
|
|
132
|
+
if (!current.has(relative)) legacy.push(relative);
|
|
133
|
+
}
|
|
134
|
+
for (const relative of new Set(legacy)) {
|
|
113
135
|
const file = destination(path.join(target.base, relative));
|
|
114
136
|
if (previous.assets.includes(relative) && exists(file)) assets.push({ destination: file, kind: 'legacy' });
|
|
115
137
|
}
|
|
@@ -120,7 +142,7 @@ function planInstallation({ agents, scope, projectDir = process.cwd(), home = os
|
|
|
120
142
|
asset.buddyCommand = asset.kind === 'command' && target.agents.some(id => ['codebuddy', 'workbuddy'].includes(id));
|
|
121
143
|
asset.scope = scope;
|
|
122
144
|
asset.personasDirectory = target.personasDirectory;
|
|
123
|
-
if (sameVersion && previous.assets.includes(
|
|
145
|
+
if (sameVersion && previous.assets.includes(assetPath(target.base, asset.destination)) && asset.source && exists(asset.destination)) {
|
|
124
146
|
asset.expectedFingerprint = fingerprint(asset.source, asset);
|
|
125
147
|
asset.reusable = fingerprint(asset.destination) === asset.expectedFingerprint;
|
|
126
148
|
}
|
|
@@ -138,7 +160,7 @@ function planInstallation({ agents, scope, projectDir = process.cwd(), home = os
|
|
|
138
160
|
return plan;
|
|
139
161
|
}
|
|
140
162
|
function executeInstallation(plan, { force = false } = {}) {
|
|
141
|
-
if (plan.conflicts.length && !force) throw new Error('
|
|
163
|
+
if (plan.conflicts.length && !force) throw new Error('已有安装内容需要确认或使用 --force 才能替换。');
|
|
142
164
|
const contexts = new Map();
|
|
143
165
|
const createdDirectories = [];
|
|
144
166
|
const committed = [];
|
|
@@ -171,8 +193,9 @@ function executeInstallation(plan, { force = false } = {}) {
|
|
|
171
193
|
fs.writeFileSync(asset.staged, manifestContent(plan, asset));
|
|
172
194
|
} else {
|
|
173
195
|
fs.cpSync(asset.source, asset.staged, { recursive: true, filter: file => {
|
|
174
|
-
if (fs.lstatSync(file).isSymbolicLink()) throw new Error(
|
|
175
|
-
return path.basename(file) !== '.DS_Store'
|
|
196
|
+
if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`包内容中包含符号链接(symlink): ${file}`);
|
|
197
|
+
return path.basename(file) !== '.DS_Store' && (asset.kind !== 'support'
|
|
198
|
+
|| !/(?:^|[\\/])(?:results|node_modules|\.git)(?:[\\/]|$)/.test(path.relative(root, file)));
|
|
176
199
|
} });
|
|
177
200
|
portable(asset.staged, asset, asset.source.endsWith('.md'));
|
|
178
201
|
}
|
|
@@ -184,12 +207,12 @@ function executeInstallation(plan, { force = false } = {}) {
|
|
|
184
207
|
const unchanged = fingerprint(asset.destination) === asset.expectedFingerprint
|
|
185
208
|
&& fingerprint(asset.staged) === asset.expectedFingerprint;
|
|
186
209
|
if (unchanged) continue;
|
|
187
|
-
if (!force) throw new Error(
|
|
210
|
+
if (!force) throw new Error(`安装期间目标已改变: ${asset.destination}. 请重新检查并确认替换后重试。`);
|
|
188
211
|
} else if (asset.mergeable) {
|
|
189
|
-
if (fingerprint(asset.destination) !== asset.observedFingerprint && !force) throw new Error(
|
|
212
|
+
if (fingerprint(asset.destination) !== asset.observedFingerprint && !force) throw new Error(`安装期间清单已改变: ${asset.destination}. 请重新检查并确认替换后重试。`);
|
|
190
213
|
}
|
|
191
214
|
// A destination may have appeared since planning; never replace it implicitly.
|
|
192
|
-
if (exists(asset.destination) && !force && !asset.mergeable) throw new Error(
|
|
215
|
+
if (exists(asset.destination) && !force && !asset.mergeable) throw new Error(`安装期间出现了新的目标: ${asset.destination}. 请重新检查并确认替换后重试。`);
|
|
193
216
|
const change = { destination: asset.destination, written: false };
|
|
194
217
|
if (exists(asset.destination)) {
|
|
195
218
|
change.backup = path.join(context.backup, path.relative(asset.base, asset.destination));
|
package/bin/native-content.cjs
CHANGED
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
function replaceBlock(content, name, replacement) {
|
|
3
|
+
const start = `<!-- i3:${name}:start -->`, end = `<!-- i3:${name}:end -->`;
|
|
4
|
+
if (content.split(start).length !== 2 || content.split(end).length !== 2 || content.indexOf(start) >= content.indexOf(end)) {
|
|
5
|
+
throw new Error(`i3-ship 宿主适配标记 ${name} 缺失、重复或顺序错误,请重新安装完整发行包。`);
|
|
6
|
+
}
|
|
7
|
+
const from = content.indexOf(start), to = content.indexOf(end) + end.length;
|
|
8
|
+
return content.slice(0, from) + `${start}\n${replacement}\n${end}` + content.slice(to);
|
|
9
|
+
}
|
|
10
|
+
// Adapt stable blocks, never their prose. Shared project commands work for
|
|
11
|
+
// either Buddy client; global paths belong to the selected profile.
|
|
5
12
|
function nativeContent(content, asset) {
|
|
13
|
+
if (asset.kind === 'support') return content;
|
|
6
14
|
content = content.replaceAll('i3-composer-agent:', '').replaceAll('agent-skills:', '');
|
|
7
15
|
if (!asset.buddyCommand) return content;
|
|
8
16
|
const projectPersonas = '.codebuddy/agents/';
|
|
9
|
-
const userPersonas = asset.scope === 'project'
|
|
10
|
-
? "the current host's user-level agents directory"
|
|
17
|
+
const userPersonas = asset.scope === 'project' ? '当前宿主的用户级 agents 目录'
|
|
11
18
|
: `\`${asset.personasDirectory.replaceAll('\\', '/')}/\``;
|
|
12
19
|
content = content.replaceAll('CLAUDE.md', 'CODEBUDDY.md')
|
|
13
20
|
.replaceAll('`~/.claude/agents/`', userPersonas)
|
|
14
21
|
.replaceAll('.claude/agents/', projectPersonas);
|
|
15
22
|
if (!asset.source.endsWith('i3-ship.md')) return content;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'Use the host\'s native subagent capability to run three specialist personas concurrently. Issue all three calls together when the host supports parallel execution.')
|
|
19
|
-
.replace("In Claude Code, each call passes `subagent_type` matching the persona's `name` field:",
|
|
20
|
-
'Select the following persona names using the host\'s native subagent mechanism:')
|
|
21
|
-
.replace('In other harnesses without an Agent tool, invoke each persona\'s system prompt sequentially and treat their outputs as if returned in parallel — the merge phase still works.',
|
|
22
|
-
'If native subagents are unavailable, read each persona prompt from the installed agents directory, perform the three reviews sequentially, and retain the same merge phase.')
|
|
23
|
-
.replace(/Constraints \(from Claude Code's subagent model\):\n[\s\S]*?\n\n\*\*Persona resolution\.\*\*[^\n]*/,
|
|
24
|
-
`Constraints:\n- Each specialist reviews independently and returns a report to this session.\n- Specialists do not delegate to one another.\n- Follow the host's execution and permission limits.\n\n**Persona resolution.** Read installed persona prompts from \`${asset.scope === 'project' ? projectPersonas : asset.personasDirectory.replaceAll('\\', '/') + '/'}\`. For project installations, check ${userPersonas} for the current host's user-defined personas, and follow its native precedence rules.`)
|
|
25
|
-
.replace('The three Phase A personas run in parallel — never sequentially.',
|
|
26
|
-
'Run the three Phase A personas in parallel when native subagents are available; otherwise use the sequential fallback above.');
|
|
27
|
-
}
|
|
23
|
+
const personas = asset.scope === 'project' ? projectPersonas : asset.personasDirectory.replaceAll('\\', '/') + '/';
|
|
24
|
+
content = replaceBlock(content, 'ship-dispatch', `使用宿主的原生子代理能力同时启动三个专业角色;支持并行调用时,在同一轮发出三个调用。
|
|
28
25
|
|
|
26
|
+
1. **\`code-reviewer\`**:对待发布改动进行正确性、可读性、架构、安全、性能五维审查,返回标准评审报告。
|
|
27
|
+
2. **\`security-auditor\`**:审查漏洞、威胁模型、OWASP Top 10、密钥、认证授权及依赖 CVE,返回标准安全报告。
|
|
28
|
+
3. **\`test-engineer\`**:检查正常、边界、错误和并发路径及测试覆盖,返回标准覆盖分析。
|
|
29
|
+
|
|
30
|
+
如果宿主的原生子代理不可用,读取安装后的角色提示,依次完成三个独立审查,保留各自报告;之后仍由主会话汇总。
|
|
31
|
+
|
|
32
|
+
约束:
|
|
33
|
+
- 角色独立审查,只返回报告,不互相委派。
|
|
34
|
+
- 遵守当前宿主的执行、上下文和权限限制。
|
|
35
|
+
- 角色解析:读取 \`${personas}\`。项目安装另按宿主优先级检查${userPersonas}的自定义角色。
|
|
36
|
+
- 后续主会话合并重复发现,按原门槛输出 GO/NO-GO;必须提供回滚计划。严重(Critical)问题默认 NO-GO,除非用户明确接受风险。`);
|
|
37
|
+
return replaceBlock(content, 'ship-parallel-rule', '宿主支持原生子代理时,阶段 A 的三个角色并行运行;不支持时采用上文的顺序降级。');
|
|
38
|
+
}
|
|
29
39
|
module.exports = { nativeContent };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const registry = require('./agent-targets.json');
|
|
3
|
+
const commonIds = ['codebuddy', 'workbuddy', 'codex', 'claude-code', 'cursor', 'gemini-cli'];
|
|
4
|
+
const orderedAgents = [...commonIds.map(id => registry.agents.find(agent => agent.id === id)),
|
|
5
|
+
...registry.agents.filter(agent => !commonIds.includes(agent.id))];
|
|
6
|
+
if (orderedAgents.some(agent => !agent) || new Set(orderedAgents.map(agent => agent.id)).size !== registry.agents.length) {
|
|
7
|
+
throw new Error('安装目录排序与工具登记不一致。');
|
|
8
|
+
}
|
|
9
|
+
module.exports = { orderedAgents, commonIds };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('node:readline');
|
|
4
|
+
const { orderedAgents, commonIds } = require('./tool-menu.cjs');
|
|
5
|
+
|
|
6
|
+
// Terminal columns are not JavaScript string lengths: Chinese characters use
|
|
7
|
+
// two cells. Keep every rendered line within the viewport before repainting.
|
|
8
|
+
function cellWidth(character) {
|
|
9
|
+
const code = character.codePointAt(0);
|
|
10
|
+
if (/\p{Mark}/u.test(character) || code === 0x200d) return 0;
|
|
11
|
+
return code >= 0x1100 && (code <= 0x115f || code === 0x2329 || code === 0x232a
|
|
12
|
+
|| (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f)
|
|
13
|
+
|| (code >= 0xac00 && code <= 0xd7a3) || (code >= 0xf900 && code <= 0xfaff)
|
|
14
|
+
|| (code >= 0xfe10 && code <= 0xfe19) || (code >= 0xfe30 && code <= 0xfe6f)
|
|
15
|
+
|| (code >= 0xff00 && code <= 0xff60) || (code >= 0xffe0 && code <= 0xffe6)
|
|
16
|
+
|| (code >= 0x1f300 && code <= 0x1faff) || (code >= 0x20000 && code <= 0x3fffd)) ? 2 : 1;
|
|
17
|
+
}
|
|
18
|
+
function width(text) { return [...text].reduce((size, character) => size + cellWidth(character), 0); }
|
|
19
|
+
function clip(text, columns) {
|
|
20
|
+
if (width(text) <= columns) return text;
|
|
21
|
+
const suffix = columns >= 3 ? '...' : '';
|
|
22
|
+
let result = '', size = 0;
|
|
23
|
+
for (const character of text) {
|
|
24
|
+
size += cellWidth(character);
|
|
25
|
+
if (size > columns - suffix.length) break;
|
|
26
|
+
result += character;
|
|
27
|
+
}
|
|
28
|
+
return result + suffix;
|
|
29
|
+
}
|
|
30
|
+
function wrap(text, columns) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
let line = '', size = 0;
|
|
33
|
+
for (const character of text) {
|
|
34
|
+
const cells = cellWidth(character);
|
|
35
|
+
if (line && size + cells > columns) { lines.push(line); line = ''; size = 0; }
|
|
36
|
+
line += character; size += cells;
|
|
37
|
+
}
|
|
38
|
+
lines.push(line);
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function selectTools(initialSelection, input = process.stdin, output = process.stdout) {
|
|
43
|
+
const selected = new Set(initialSelection);
|
|
44
|
+
let view = 'common', cursor = 0, commonCursor = 0, query = '', searching = false, message = '';
|
|
45
|
+
let frame = [], pageSize = 10, finished = false;
|
|
46
|
+
const wasRaw = Boolean(input.isRaw), wasFlowing = input.readableFlowing;
|
|
47
|
+
const choices = () => view === 'common' ? [...orderedAgents.slice(0, commonIds.length), null]
|
|
48
|
+
: view === 'more' ? orderedAgents.slice(commonIds.length)
|
|
49
|
+
: orderedAgents.filter(agent => `${agent.label} ${agent.id}`.toLowerCase().includes(query.toLowerCase()));
|
|
50
|
+
const selectedLabels = () => [...selected].map(id => orderedAgents.find(agent => agent.id === id).label).join('、');
|
|
51
|
+
|
|
52
|
+
function clearFrame() {
|
|
53
|
+
if (!frame.length) return;
|
|
54
|
+
const columns = output.columns || 80;
|
|
55
|
+
const rows = frame.reduce((count, line) => count + Math.max(1, Math.ceil(width(line) / columns)), 0);
|
|
56
|
+
readline.moveCursor(output, 0, -rows);
|
|
57
|
+
readline.cursorTo(output, 0);
|
|
58
|
+
readline.clearScreenDown(output);
|
|
59
|
+
frame = [];
|
|
60
|
+
}
|
|
61
|
+
function render() {
|
|
62
|
+
if (finished) return;
|
|
63
|
+
const columns = Math.max(1, (output.columns || 80) - 1), rows = output.rows || 24;
|
|
64
|
+
const entries = choices();
|
|
65
|
+
cursor = Math.max(0, Math.min(cursor, entries.length - 1));
|
|
66
|
+
const summary = wrap(`已选(${selected.size}):${selectedLabels() || '无'}`, columns);
|
|
67
|
+
const summaryLimit = rows < 16 ? 1 : 3;
|
|
68
|
+
if (summary.length > summaryLimit) {
|
|
69
|
+
summary.length = summaryLimit;
|
|
70
|
+
summary[summaryLimit - 1] = clip(summary[summaryLimit - 1], Math.max(0, columns - 3)) + '...';
|
|
71
|
+
}
|
|
72
|
+
const hints = searching
|
|
73
|
+
? ['输入关键词实时搜索', 'Enter/↓ 浏览结果 · Esc返回 · Ctrl-C取消']
|
|
74
|
+
: ['↑↓ 移动/跨页 · 空格勾选 · Enter确认', 'm 更多 · / 搜索 · b/← 返回 · q/Esc取消'];
|
|
75
|
+
const footer = ['', ...summary, ...hints.flatMap(line => wrap(line, columns)), message];
|
|
76
|
+
const searchLine = view === 'search' ? [`搜索:${query}${searching ? '|' : ''}`] : [];
|
|
77
|
+
pageSize = Math.max(1, Math.min(10, rows - footer.length - searchLine.length - 2));
|
|
78
|
+
const page = Math.floor(cursor / pageSize), pages = Math.max(1, Math.ceil(entries.length / pageSize));
|
|
79
|
+
const title = view === 'common' ? '选择常用工具(支持多选):'
|
|
80
|
+
: `${view === 'more' ? '更多工具' : '搜索结果'}:第 ${page + 1}/${pages} 页,共 ${entries.length} 项`;
|
|
81
|
+
const lines = [title, ...searchLine];
|
|
82
|
+
for (let index = page * pageSize; index < Math.min(entries.length, (page + 1) * pageSize); index++) {
|
|
83
|
+
const agent = entries[index], pointer = index === cursor ? '>' : ' ';
|
|
84
|
+
lines.push(agent ? `${pointer} [${selected.has(agent.id) ? 'x' : ' '}] ${orderedAgents.indexOf(agent) + 1}. ${agent.label} [${agent.id}]${agent.globalSkillsDir ? '' : '(仅项目)'}`
|
|
85
|
+
: `${pointer} 更多工具(空格展开)`);
|
|
86
|
+
}
|
|
87
|
+
if (!entries.length) lines.push('没有匹配工具;按 / 重新搜索,或 Esc 返回。');
|
|
88
|
+
clearFrame();
|
|
89
|
+
frame = [...lines, ...footer].map(line => clip(line, columns));
|
|
90
|
+
output.write(frame.join('\n') + '\n');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
function finish(value, error) {
|
|
95
|
+
if (finished) return;
|
|
96
|
+
finished = true;
|
|
97
|
+
input.off('keypress', keypress);
|
|
98
|
+
input.off('end', cancel);
|
|
99
|
+
input.off('error', fail);
|
|
100
|
+
output.off('resize', redraw);
|
|
101
|
+
process.off('SIGINT', cancel);
|
|
102
|
+
input.setRawMode(wasRaw);
|
|
103
|
+
if (!wasFlowing) input.pause();
|
|
104
|
+
try {
|
|
105
|
+
clearFrame();
|
|
106
|
+
output.write('\x1b[?25h');
|
|
107
|
+
if (value) output.write(`已选择工具:${selectedLabels()}\n`);
|
|
108
|
+
} catch (outputError) { error ||= outputError; }
|
|
109
|
+
if (error) reject(error); else resolve(value);
|
|
110
|
+
}
|
|
111
|
+
function cancel() { finish(null); }
|
|
112
|
+
function fail(error) { finish(null, error); }
|
|
113
|
+
function redraw() { try { render(); } catch (error) { fail(error); } }
|
|
114
|
+
function openMore() {
|
|
115
|
+
if (view === 'common') commonCursor = cursor;
|
|
116
|
+
view = 'more'; cursor = 0; searching = false;
|
|
117
|
+
}
|
|
118
|
+
function back() { view = 'common'; cursor = commonCursor; searching = false; query = ''; }
|
|
119
|
+
function keypress(text, key = {}) {
|
|
120
|
+
if (finished) return;
|
|
121
|
+
try {
|
|
122
|
+
message = '';
|
|
123
|
+
if (key.ctrl && ['c', 'd'].includes(key.name)) { cancel(); return; }
|
|
124
|
+
if (searching) {
|
|
125
|
+
if (key.name === 'escape') back();
|
|
126
|
+
else if (['return', 'enter', 'down'].includes(key.name)) searching = false;
|
|
127
|
+
else if (key.name === 'backspace') { query = [...query].slice(0, -1).join(''); cursor = 0; }
|
|
128
|
+
else if (text && !key.ctrl && !key.meta && !/[\x00-\x1f\x7f]/.test(text)) { query += text; cursor = 0; }
|
|
129
|
+
} else if (key.name === 'escape') {
|
|
130
|
+
if (view === 'search') back(); else { cancel(); return; }
|
|
131
|
+
} else if (text === 'q') { cancel(); return; }
|
|
132
|
+
else if (['return', 'enter'].includes(key.name)) {
|
|
133
|
+
if (selected.size) { finish([...selected]); return; }
|
|
134
|
+
message = '请至少用空格选择一个工具。';
|
|
135
|
+
} else if (key.name === 'space' || text === ' ') {
|
|
136
|
+
const entry = choices()[cursor];
|
|
137
|
+
if (entry) { if (selected.has(entry.id)) selected.delete(entry.id); else selected.add(entry.id); }
|
|
138
|
+
else if (view === 'common') openMore();
|
|
139
|
+
} else if (['up', 'down', 'pageup', 'pagedown'].includes(key.name) || ['n', 'p'].includes(text)) {
|
|
140
|
+
const direction = ['up', 'pageup'].includes(key.name) || text === 'p' ? -1 : 1;
|
|
141
|
+
const step = ['pageup', 'pagedown'].includes(key.name) || ['n', 'p'].includes(text) ? pageSize : 1;
|
|
142
|
+
cursor = Math.max(0, Math.min(choices().length - 1, cursor + direction * step));
|
|
143
|
+
} else if (text === 'm' || (key.name === 'right' && view === 'common')) openMore();
|
|
144
|
+
else if (text === 'b' || key.name === 'left') back();
|
|
145
|
+
else if (text === '/') { if (view === 'common') commonCursor = cursor; view = 'search'; query = ''; cursor = 0; searching = true; }
|
|
146
|
+
render();
|
|
147
|
+
} catch (error) { fail(error); }
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
readline.emitKeypressEvents(input);
|
|
151
|
+
input.setRawMode(true);
|
|
152
|
+
input.on('keypress', keypress);
|
|
153
|
+
input.once('end', cancel);
|
|
154
|
+
input.once('error', fail);
|
|
155
|
+
output.on('resize', redraw);
|
|
156
|
+
process.once('SIGINT', cancel);
|
|
157
|
+
output.write('\x1b[?25l');
|
|
158
|
+
render();
|
|
159
|
+
input.resume();
|
|
160
|
+
} catch (error) { fail(error); }
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { selectTools };
|