@devcxl/opencode-cabbage 1.2.0 → 2.0.0

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.
Files changed (104) hide show
  1. package/.codex-plugin/plugin.json +36 -0
  2. package/README.md +67 -21
  3. package/README.zh.md +26 -13
  4. package/assets/agents/dev-lifecycle.md +6 -0
  5. package/assets/prompts/bootstrap.md +4 -0
  6. package/assets/skills/flow-change/SKILL.md +109 -0
  7. package/assets/skills/flow-design/SKILL.md +15 -0
  8. package/assets/skills/flow-docs/SKILL.md +112 -0
  9. package/assets/skills/flow-incident/SKILL.md +98 -0
  10. package/assets/skills/flow-research/SKILL.md +9 -0
  11. package/assets/skills/flow-setup/SKILL.md +13 -0
  12. package/codex-skills/agent-architect/SKILL.md +79 -0
  13. package/codex-skills/agent-dev-lifecycle/SKILL.md +311 -0
  14. package/codex-skills/agent-developer/SKILL.md +51 -0
  15. package/codex-skills/agent-goal-verify/SKILL.md +45 -0
  16. package/codex-skills/agent-researcher/SKILL.md +32 -0
  17. package/codex-skills/agent-reviewer/SKILL.md +109 -0
  18. package/codex-skills/flow-change/SKILL.md +109 -0
  19. package/codex-skills/flow-code/SKILL.md +71 -0
  20. package/codex-skills/flow-design/SKILL.md +161 -0
  21. package/codex-skills/flow-docs/SKILL.md +112 -0
  22. package/codex-skills/flow-incident/SKILL.md +98 -0
  23. package/codex-skills/flow-release/SKILL.md +90 -0
  24. package/codex-skills/flow-requirements/SKILL.md +90 -0
  25. package/codex-skills/flow-research/SKILL.md +86 -0
  26. package/codex-skills/flow-review/SKILL.md +110 -0
  27. package/codex-skills/flow-setup/SKILL.md +88 -0
  28. package/codex-skills/flow-tasks/SKILL.md +220 -0
  29. package/codex-skills/flow-tdd/SKILL.md +235 -0
  30. package/dist/hooks/session-start.d.ts +1 -0
  31. package/dist/hooks/session-start.js +72 -0
  32. package/dist/hooks/session-start.js.map +1 -0
  33. package/dist/index.d.ts +2 -5
  34. package/dist/index.js +1 -4
  35. package/dist/index.js.map +1 -1
  36. package/dist/plugin/agents.d.ts +20 -0
  37. package/dist/plugin/agents.js +79 -0
  38. package/dist/plugin/agents.js.map +1 -1
  39. package/dist/plugin/cabbage-cli.d.ts +28 -0
  40. package/dist/plugin/cabbage-cli.js +92 -0
  41. package/dist/plugin/cabbage-cli.js.map +1 -0
  42. package/dist/plugin/cabbage-tool.d.ts +103 -0
  43. package/dist/plugin/cabbage-tool.js +169 -0
  44. package/dist/plugin/cabbage-tool.js.map +1 -0
  45. package/dist/plugin/commands.d.ts +10 -0
  46. package/dist/plugin/commands.js +1 -1
  47. package/dist/plugin/commands.js.map +1 -1
  48. package/dist/plugin/continuation.d.ts +22 -0
  49. package/dist/plugin/continuation.js +233 -0
  50. package/dist/plugin/continuation.js.map +1 -0
  51. package/dist/plugin/goal-store.d.ts +64 -0
  52. package/dist/plugin/goal-store.js +57 -0
  53. package/dist/plugin/goal-store.js.map +1 -0
  54. package/dist/plugin/goal.d.ts +95 -38
  55. package/dist/plugin/goal.js +208 -199
  56. package/dist/plugin/goal.js.map +1 -1
  57. package/dist/plugin/install-dir.d.ts +13 -0
  58. package/dist/plugin/install-dir.js +46 -0
  59. package/dist/plugin/install-dir.js.map +1 -0
  60. package/dist/plugin/server.d.ts +4 -12
  61. package/dist/plugin/server.js +279 -423
  62. package/dist/plugin/server.js.map +1 -1
  63. package/dist/plugin/session-abort.d.ts +5 -0
  64. package/dist/plugin/session-abort.js +26 -0
  65. package/dist/plugin/session-abort.js.map +1 -0
  66. package/dist/plugin/skills.d.ts +15 -0
  67. package/dist/plugin/skills.js +47 -50
  68. package/dist/plugin/skills.js.map +1 -1
  69. package/dist/plugin.d.ts +2 -2
  70. package/dist/plugin.js +6 -2
  71. package/dist/plugin.js.map +1 -1
  72. package/dist/util/paths.d.ts +0 -6
  73. package/dist/util/paths.js +0 -15
  74. package/dist/util/paths.js.map +1 -1
  75. package/hooks/hooks.json +17 -0
  76. package/package.json +13 -8
  77. package/vendor/cabbage/MANIFEST.json +8 -0
  78. package/vendor/cabbage/cabbage_cli/__init__.py +1 -0
  79. package/vendor/cabbage/cabbage_cli/__main__.py +2 -0
  80. package/vendor/cabbage/cabbage_cli/assets/docs-site/.vitepress/config.ts +78 -0
  81. package/vendor/cabbage/cabbage_cli/assets/docs-site/README.md +31 -0
  82. package/vendor/cabbage/cabbage_cli/assets/docs-site/package.json +16 -0
  83. package/vendor/cabbage/cabbage_cli/assets/docs-site/pnpm-lock.yaml +2504 -0
  84. package/vendor/cabbage/cabbage_cli/assets/integrations/cabbage.yml +127 -0
  85. package/vendor/cabbage/cabbage_cli/assets/templates/adr.md +47 -0
  86. package/vendor/cabbage/cabbage_cli/assets/templates/api-design.md +45 -0
  87. package/vendor/cabbage/cabbage_cli/assets/templates/change-record.md +21 -0
  88. package/vendor/cabbage/cabbage_cli/assets/templates/database-design.md +37 -0
  89. package/vendor/cabbage/cabbage_cli/assets/templates/generic.md +20 -0
  90. package/vendor/cabbage/cabbage_cli/assets/templates/impact.md +41 -0
  91. package/vendor/cabbage/cabbage_cli/assets/templates/incident.md +48 -0
  92. package/vendor/cabbage/cabbage_cli/assets/templates/postmortem.md +43 -0
  93. package/vendor/cabbage/cabbage_cli/assets/templates/prd.md +71 -0
  94. package/vendor/cabbage/cabbage_cli/assets/templates/release-plan.md +45 -0
  95. package/vendor/cabbage/cabbage_cli/assets/templates/rfc.md +44 -0
  96. package/vendor/cabbage/cabbage_cli/assets/templates/security-review.md +35 -0
  97. package/vendor/cabbage/cabbage_cli/assets/templates/tasks.md +62 -0
  98. package/vendor/cabbage/cabbage_cli/assets/templates/tech-spec.md +87 -0
  99. package/vendor/cabbage/cabbage_cli/assets/templates/test-plan.md +44 -0
  100. package/vendor/cabbage/cabbage_cli/assets/workflows/change.yaml +11 -0
  101. package/vendor/cabbage/cabbage_cli/assets/workflows/incident.yaml +4 -0
  102. package/vendor/cabbage/cabbage_cli/cli.py +283 -0
  103. package/vendor/cabbage/cabbage_cli/core.py +606 -0
  104. package/vendor/cabbage/cabbage_cli/scaffold.py +313 -0
@@ -0,0 +1,235 @@
1
+ ---
2
+ name: flow-tdd
3
+ description: 公共 Test Seam 上的行为 TDD — RED→GREEN 状态机 + self-report 证据
4
+ ---
5
+
6
+ # flow-tdd
7
+
8
+ TDD(Test-Driven Development)Prompt 协议,为所有编码阶段提供统一的 TDD 流程约束。
9
+ 本 skill 是 TDD 协议的唯一来源,其他 skills 和 agents 通过引用本协议获得一致的 TDD 行为。
10
+
11
+ ## 核心原则
12
+
13
+ 测试是行为规格:通过 Design `Testing Decisions` 约定的公共 Test Seam 验证可观察行为,而不是验证内部实现。只要公共行为不变,重构 Module 内部实现不应导致测试失败。
14
+
15
+ 每个 TDD cycle 是一个最窄行为切片:
16
+
17
+ ```text
18
+ 确定 Test Seam 与一个行为
19
+ ↓
20
+ 写一个行为测试
21
+ ↓
22
+ RED
23
+ ↓
24
+ 最小实现
25
+ ↓
26
+ GREEN
27
+ ↓
28
+ 下一个行为
29
+ ```
30
+
31
+ ### 好测试
32
+
33
+ - 名称描述调用者或用户可以完成什么,而不是调用了哪个内部方法。
34
+ - 只通过约定的公共 Test Seam 触发并观察行为。
35
+ - 期望值来自 Acceptance Criteria、已知正确示例或其他独立来源。
36
+ - 实现重构但行为不变时仍然通过。
37
+
38
+ ### 反模式
39
+
40
+ - **实现耦合** — 测试私有方法、内部协作对象、调用次数或调用顺序。
41
+ - **侧信道验证** — 绕过公共 Seam 读取数据库或内部状态来证明行为。
42
+ - **循环论证** — 用与实现相同的算法重新计算期望值。
43
+ - **水平批量** — 先写完全部测试再实现;应按一个行为测试 → 一个最小实现逐次推进。
44
+ - **为 mock 造抽象** — 仅允许在真实系统边界按既有 Interface 替换外部依赖,不为测试方便新增生产 Seam。
45
+
46
+ ## Advisory Procedure
47
+
48
+ Agent 自行遵循 TDD 流程并 self-report 状态。测试质量由仓库 CI 把关,内核不强制证据。
49
+
50
+ ### Cycle 状态机
51
+
52
+ ```
53
+ ┌─────────────┐
54
+ │ cycle-start │ ← 开始新的 TDD 循环
55
+ └──────┬──────┘
56
+ │
57
+ ▼
58
+ ┌─────────────┐
59
+ │ red │ ← 编写/更新测试,验证测试失败(RED)
60
+ └──────┬──────┘
61
+ │
62
+ ▼
63
+ ┌─────────────┐
64
+ │ green │ ← 最小实现使测试通过(GREEN),仅做局部行为保持整理
65
+ └──────┬──────┘
66
+ │
67
+ ┌──────────┼──────────┐
68
+ ▼ ▼ ▼
69
+ ┌─────────┐ ┌─────────┐ ┌──────────────┐
70
+ │ 下一个 │ │ abandon │ │ 所有 cycle │
71
+ │ cycle │ │ -cycle │ │ 完成 │
72
+ └─────────┘ └─────────┘ └──────┬───────┘
73
+ │
74
+ ▼
75
+ ┌──────────────────┐
76
+ │ final-regression │ ← 运行全部测试,确认无回归
77
+ └────────┬─────────┘
78
+ │
79
+ ▼
80
+ ┌───────────────────┐
81
+ │ final-verification│ ← 对照 Acceptance Criteria 逐条验证
82
+ └───────────────────┘
83
+ ```
84
+
85
+ ### cycle-start
86
+
87
+ 开始一个新的 RED→GREEN 循环。Agent 在实现前从 Design `Testing Decisions` 读取 Test Seam,并声明本 cycle 的一个可观察行为。
88
+
89
+ **self-report 格式:**
90
+ ```
91
+ ## TDD cycle-start
92
+ - cycle: <序号>
93
+ - behavior: <本 cycle 要交付的可观察行为>
94
+ - test_seam: <约定的公共 Test Seam>
95
+ - test: <通过该 Seam 验证行为的测试名称>
96
+ ```
97
+
98
+ ### red
99
+
100
+ 编写或更新测试用例,验证测试在当前实现下失败。
101
+
102
+ **约束:**
103
+ - 测试必须有明确的 fail/pass 边界
104
+ - 测试名称描述行为,且只通过 cycle-start 声明的 Test Seam 触发和观察结果
105
+ - RED 必须因目标行为尚未实现而失败,不能把环境、语法或夹具错误当作证据
106
+ - 测试运行命令必须与 Task 的 `test_commands` 中定义的一致
107
+ - 记录测试失败输出作为 RED evidence
108
+
109
+ **self-report 格式:**
110
+ ```
111
+ ## TDD red
112
+ - test: <测试名称>
113
+ - command: <运行命令>
114
+ - result: FAIL(预期)
115
+ ```
116
+
117
+ ### green
118
+
119
+ 编写最小实现使 RED 阶段的行为测试通过。可进行局部、行为保持的整理,但不得改变约定的 Test Seam 或扩展范围。
120
+
121
+ **约束:**
122
+ - 只写使当前测试通过的最少代码
123
+ - 不新增与当前 test 无关的功能
124
+ - 重构仅限于当前 cycle 涉及的代码
125
+ - 不为通过测试而暴露内部状态、增加仅供测试使用的生产接口或改成实现细节断言
126
+
127
+ **self-report 格式:**
128
+ ```
129
+ ## TDD green
130
+ - test: <测试名称>
131
+ - command: <运行命令>
132
+ - result: PASS
133
+ - changes: <涉及的文件列表>
134
+ ```
135
+
136
+ ### abandon-cycle
137
+
138
+ 当发现当前 cycle 的目标不合理、设计有缺陷或需要回退时,废弃当前 cycle 的所有修改。
139
+
140
+ **触发条件:**
141
+ - RED 阶段发现测试设计有误
142
+ - GREEN 阶段发现需要根本性重新设计
143
+ - 发现更简单的替代方案
144
+
145
+ **流程:**
146
+ 1. 放弃当前 cycle 的修改(`git checkout -- <files>` 或 `git stash drop`)
147
+ 2. 记录 abandon 原因
148
+ 3. 重新从 cycle-start 开始
149
+
150
+ **self-report 格式:**
151
+ ```
152
+ ## TDD abandon-cycle
153
+ - cycle: <序号>
154
+ - reason: <废弃原因>
155
+ ```
156
+
157
+ ### final-regression
158
+
159
+ 所有 cycle 完成后,运行项目全部测试套件,确认无回归。
160
+
161
+ **约束:**
162
+ - 必须运行 Task `test_commands` 中定义的全部命令
163
+ - 所有测试必须通过
164
+ - 如有失败 → 修复(不要求新 cycle,但需记录修复内容)
165
+
166
+ **self-report 格式:**
167
+ ```
168
+ ## TDD final-regression
169
+ - passed: <通过的测试数>
170
+ - failed: <失败的测试数>
171
+ - total: <测试总数>
172
+ ```
173
+
174
+ ### final-verification
175
+
176
+ 对照 Task 的 `Acceptance Criteria` 逐条验证,确认每项都由公共 Test Seam 上的行为测试或 `verify_commands` 证明。
177
+
178
+ **self-report 格式:**
179
+ ```
180
+ ## TDD final-verification
181
+ - criteria_total: <总数>
182
+ - criteria_met: <已满足>
183
+ - criteria_pending: <未满足>
184
+ ```
185
+
186
+ ## Contract
187
+
188
+ ### Trigger
189
+ 由编码阶段(`flow-code`)自动触发。Agent 在开始编码任务时加载 `flow-tdd` skill 获取 TDD 流程约束。
190
+
191
+ ### Inputs
192
+ - Task 文件中的 `Builds` 与 `Acceptance Criteria`(来源:`flow-tasks`)
193
+ - Task frontmatter 中的 `test_commands` 与 `verify_commands`
194
+ - Design `Testing Decisions` 中约定的 Test Seam 与 Observable Result
195
+
196
+ ### Preconditions
197
+ - Task 文件存在,包含 `Builds`、`Acceptance Criteria`、`test_commands`
198
+ - Design 已为目标行为定义公共 Test Seam;存量 Design 缺失时先向 primary 返回 Design Gap,由兼容门在当前 stage 补齐
199
+ - 测试运行环境就绪(worktree 内依赖已安装)
200
+
201
+ ### Procedure
202
+ 1. 读取 Task 的 `Builds`、`Acceptance Criteria`、`test_commands`、`verify_commands`。
203
+ 2. 读取 Design `Testing Decisions`,确认目标行为的 Test Seam 与 Observable Result。
204
+ 3. 选择一个尚未满足的行为,执行 `cycle-start` 声明 behavior、test_seam 与 test。
205
+ 4. 执行 `red` → 只写该行为测试,验证因行为缺失而失败,记录 self-report。
206
+ 5. 执行 `green` → 编写最小实现,验证通过,记录 self-report。
207
+ 6. 按一个行为切片一个 cycle 重复,直到所有 Acceptance Criteria 被覆盖。
208
+ 7. 执行 `final-regression` → 运行全部测试。
209
+ 8. 执行 `final-verification` → 通过 Test Seam 或 `verify_commands` 逐条验证 Acceptance Criteria。
210
+
211
+ ### Outputs
212
+ - 每个 cycle 的 self-report
213
+ - `final-regression` 报告(测试通过/失败统计)
214
+ - `final-verification` 报告(criterion 覆盖情况)
215
+
216
+ ### Failure
217
+ - RED 阶段测试意外通过 → 检查测试是否有意义,可能需要 abandon-cycle
218
+ - RED 因环境、语法或夹具错误失败 → 修复测试基础设施,不能计为 RED evidence
219
+ - 只能通过内部实现观察目标行为 → 停止编码并向 primary 返回 Design Gap,不自行修改 Design、不回退 lifecycle stage
220
+ - GREEN 阶段测试持续失败 → 检查实现逻辑,记录失败原因
221
+ - final-regression 失败 → 修复回归,不要求新 cycle
222
+ - final-verification 未全部满足 → 返回未完成的 criterion,补充 cycle
223
+
224
+ ### Idempotency
225
+ - 同一 cycle 重复执行 → 以最后一次结果为准
226
+ - final-regression 重复执行 → 覆盖上次结果
227
+
228
+ ### Prohibited Actions
229
+ - 不跳过 RED 阶段直接进入 GREEN
230
+ - 不测试私有方法、内部调用次数、内部调用顺序或未约定的侧信道
231
+ - 不一次批量编写多个行为测试后再统一实现
232
+ - 不为了 mock 或测试方便新增生产抽象
233
+ - 不跳过 final-regression 直接 commit
234
+ - 不在 abandon-cycle 后保留修改
235
+ - 不伪造测试结果(实际运行命令后再 self-report)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Cabbage Session Start Hook — loads plugin overview and project context.
3
+ * Runs on Codex SessionStart lifecycle event (matcher: startup|resume).
4
+ * Compiled to dist/hooks/session-start.js by tsc.
5
+ */
6
+ import { readFileSync, existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ // PLUGIN_ROOT is set by Codex hooks runtime
9
+ const PROJECT_DIR = process.cwd();
10
+ const MAX_OUTPUT_LENGTH = 12000;
11
+ function getHeader() {
12
+ return `## Cabbage Development Plugin
13
+
14
+ This plugin provides a full development lifecycle orchestration system.
15
+ Load skills by name: @agent-dev-lifecycle, @agent-architect, @agent-developer, @agent-reviewer, @agent-goal-verify, @agent-researcher
16
+
17
+ ### Available Flow Skills
18
+ - \`@flow-setup\` — 初始化项目开发环境
19
+ - \`@flow-requirements\` — 需求分析产出 PRD
20
+ - \`@flow-design\` — 技术方案与 ADR
21
+ - \`@flow-tasks\` — DAG 任务拆解
22
+ - \`@flow-research\` — 调研/事实核查
23
+ - \`@flow-code\` — 编码实现(TDD)
24
+ - \`@flow-tdd\` — TDD 协议参考
25
+ - \`@flow-review\` — 代码审查
26
+ - \`@flow-release\` — 发布流程
27
+
28
+ ### Available Agent Skills
29
+ - \`@agent-dev-lifecycle\` — 全流程编排器(主 agent,场景分诊)
30
+ - \`@agent-architect\` — 架构设计
31
+ - \`@agent-developer\` — 编码实现
32
+ - \`@agent-reviewer\` — 代码审查
33
+ - \`@agent-goal-verify\` — 目标验证
34
+ - \`@agent-researcher\` — 独立调研
35
+ `;
36
+ }
37
+ function readProjectProfile() {
38
+ // Try root AGENTS.md from project directory
39
+ const agentsMd = join(PROJECT_DIR, "AGENTS.md");
40
+ if (!existsSync(agentsMd))
41
+ return "";
42
+ let content;
43
+ try {
44
+ content = readFileSync(agentsMd, "utf8");
45
+ }
46
+ catch {
47
+ return "";
48
+ }
49
+ // Match only the exact "## Project Profile" heading line (not "## Project Profile Notes"),
50
+ // and stop at the next "## " heading or end of file.
51
+ const lines = content.split("\n");
52
+ const start = lines.findIndex((line) => line.trim() === "## Project Profile");
53
+ if (start === -1)
54
+ return "";
55
+ const end = lines.findIndex((line, i) => i > start && /^##\s/.test(line.trim()));
56
+ const slice = end === -1 ? lines.slice(start) : lines.slice(start, end);
57
+ return slice.join("\n").trim();
58
+ }
59
+ function main() {
60
+ const header = getHeader();
61
+ const profile = readProjectProfile();
62
+ const parts = [header];
63
+ if (profile)
64
+ parts.push(`## Project Context\n\n${profile}`);
65
+ let output = parts.join("\n\n");
66
+ if (output.length > MAX_OUTPUT_LENGTH) {
67
+ output = `${output.slice(0, MAX_OUTPUT_LENGTH)}\n\n[context truncated — output exceeds ${MAX_OUTPUT_LENGTH} chars]`;
68
+ }
69
+ console.log(output);
70
+ }
71
+ main();
72
+ //# sourceMappingURL=session-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../src/hooks/session-start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,4CAA4C;AAC5C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;AACjC,MAAM,iBAAiB,GAAG,KAAK,CAAA;AAE/B,SAAS,SAAS;IAChB,OAAO;;;;;;;;;;;;;;;;;;;;;;;CAuBR,CAAA;AACD,CAAC;AAED,SAAS,kBAAkB;IACzB,4CAA4C;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;IAC/C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAA;IAEpC,IAAI,OAAe,CAAA;IACnB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;IAED,2FAA2F;IAC3F,qDAAqD;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,oBAAoB,CAAC,CAAA;IAC7E,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAE3B,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;IAChF,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IACvE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;AAChC,CAAC;AAED,SAAS,IAAI;IACX,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAC1B,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAA;IAEpC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,IAAI,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAA;IAE3D,IAAI,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,2CAA2C,iBAAiB,SAAS,CAAA;IACrH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;AACrB,CAAC;AAED,IAAI,EAAE,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,2 @@
1
- declare const _default: {
2
- id: string;
3
- server: import("@opencode-ai/plugin").Plugin;
4
- };
5
- export default _default;
1
+ import { OpencodeCabbage } from "./plugin.js";
2
+ export default OpencodeCabbage;
package/dist/index.js CHANGED
@@ -1,6 +1,3 @@
1
1
  import { OpencodeCabbage } from "./plugin.js";
2
- export default {
3
- id: "opencode-cabbage",
4
- server: OpencodeCabbage,
5
- };
2
+ export default OpencodeCabbage;
6
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,eAAe;IACb,EAAE,EAAE,kBAAkB;IACtB,MAAM,EAAE,eAAe;CACxB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,eAAe,eAAe,CAAA"}
@@ -2,6 +2,7 @@
2
2
  * Agent frontmatter 的 permission 解析。
3
3
  * 值可为字符串(如 "deny"、"npm test|git push")或嵌套规则对象
4
4
  * (OpenCode 语义:pattern → action,如 bash: { "*": "deny", "npm *": "allow" })。
5
+ * V2 注册时转换为规则数组(见 buildAgentRules)。
5
6
  */
6
7
  export interface AgentPermission {
7
8
  [key: string]: string | Record<string, string> | undefined;
@@ -12,6 +13,25 @@ export interface AgentEntry {
12
13
  mode?: "subagent" | "primary" | "all";
13
14
  color?: string;
14
15
  permission?: AgentPermission;
16
+ /** V1 tools 启停映射(write/edit/bash/read…);false 表示禁用该工具 */
17
+ tools?: Record<string, boolean>;
15
18
  prompt: string;
16
19
  }
20
+ /** V2 permission 规则(@opencode/schema PermissionRule 子集) */
21
+ export interface AgentRule {
22
+ action: string;
23
+ resource: string;
24
+ effect: "allow" | "deny" | "ask";
25
+ }
26
+ /**
27
+ * 将 agent 的 V1 风格 permission/tools 转换为 V2 规则数组。
28
+ * 顺序保持声明序(V2 最后匹配优先);tools 禁用规则置于末尾(V1 语义:工具整体禁用不可被白名单恢复)。
29
+ */
30
+ export declare function buildAgentRules(entry: AgentEntry, testCommandToken: string): AgentRule[];
31
+ /** V2 agent 注册载荷:缺失时新增,已存在则保留用户版本(V1 的 skip-if-exists 语义) */
32
+ export interface AgentPayload {
33
+ key: string;
34
+ fields: Record<string, unknown>;
35
+ }
36
+ export declare function buildAgentPayloads(agentsDir: string, testCommandToken: string): AgentPayload[];
17
37
  export declare function loadAgents(agentsDir: string): AgentEntry[];
@@ -1,6 +1,78 @@
1
1
  import { readFileSync, readdirSync, existsSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
+ /** V1 tools 名 → V2 permission action(write 工具的权限 action 实测为 "edit") */
5
+ const TOOLS_TO_PERMISSION_ACTION = {
6
+ write: "edit",
7
+ edit: "edit",
8
+ bash: "bash",
9
+ read: "read",
10
+ glob: "glob",
11
+ grep: "grep",
12
+ webfetch: "webfetch",
13
+ websearch: "websearch",
14
+ todowrite: "todowrite",
15
+ task: "subagent",
16
+ };
17
+ function normalizeEffect(value) {
18
+ return value === "allow" ? "allow" : value === "deny" ? "deny" : "ask";
19
+ }
20
+ /**
21
+ * 渲染规则中的 `<profile-test-command>` 占位符为 Profile 测试命令首 token。
22
+ * Profile 未配置(token 为空)时返回 null 表示删除该规则(避免渲染成 "*" 覆盖兜底 deny)。
23
+ */
24
+ function renderPlaceholder(pattern, testCommandToken) {
25
+ if (!pattern.includes("<profile-test-command>"))
26
+ return pattern;
27
+ if (testCommandToken === "")
28
+ return null;
29
+ return pattern.replaceAll("<profile-test-command>", testCommandToken);
30
+ }
31
+ /**
32
+ * 将 agent 的 V1 风格 permission/tools 转换为 V2 规则数组。
33
+ * 顺序保持声明序(V2 最后匹配优先);tools 禁用规则置于末尾(V1 语义:工具整体禁用不可被白名单恢复)。
34
+ */
35
+ export function buildAgentRules(entry, testCommandToken) {
36
+ const rules = [];
37
+ for (const [action, value] of Object.entries(entry.permission ?? {})) {
38
+ if (typeof value === "string") {
39
+ rules.push({ action, resource: "*", effect: normalizeEffect(value) });
40
+ continue;
41
+ }
42
+ if (!value)
43
+ continue;
44
+ for (const [pattern, raw] of Object.entries(value)) {
45
+ const rendered = renderPlaceholder(pattern, testCommandToken);
46
+ if (rendered === null)
47
+ continue;
48
+ rules.push({ action, resource: rendered, effect: normalizeEffect(raw) });
49
+ }
50
+ }
51
+ const deniedActions = new Set();
52
+ for (const [tool, enabled] of Object.entries(entry.tools ?? {})) {
53
+ if (enabled !== false)
54
+ continue;
55
+ const action = TOOLS_TO_PERMISSION_ACTION[tool] ?? tool;
56
+ if (deniedActions.has(action))
57
+ continue;
58
+ deniedActions.add(action);
59
+ rules.push({ action, resource: "*", effect: "deny" });
60
+ }
61
+ return rules;
62
+ }
63
+ export function buildAgentPayloads(agentsDir, testCommandToken) {
64
+ return loadAgents(agentsDir).map((entry) => ({
65
+ key: entry.key,
66
+ fields: {
67
+ name: entry.key,
68
+ ...(entry.description ? { description: entry.description } : {}),
69
+ ...(entry.mode ? { mode: entry.mode } : {}),
70
+ ...(entry.color ? { color: entry.color } : {}),
71
+ system: entry.prompt,
72
+ permissions: buildAgentRules(entry, testCommandToken),
73
+ },
74
+ }));
75
+ }
4
76
  function parseAgentFile(filePath) {
5
77
  const content = readFileSync(filePath, "utf8");
6
78
  const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
@@ -38,12 +110,19 @@ function parseAgentFile(filePath) {
38
110
  })
39
111
  .filter(([, v]) => v !== undefined))
40
112
  : undefined;
113
+ const toolsRaw = parsed.tools;
114
+ const tools = toolsRaw && typeof toolsRaw === "object" && !Array.isArray(toolsRaw)
115
+ ? Object.fromEntries(Object.entries(toolsRaw)
116
+ .filter(([, v]) => typeof v === "boolean")
117
+ .map(([k, v]) => [k, v]))
118
+ : undefined;
41
119
  return {
42
120
  key: name,
43
121
  description: parsed.description,
44
122
  mode: parsed.mode,
45
123
  color: parsed.color,
46
124
  permission,
125
+ tools,
47
126
  prompt: body,
48
127
  };
49
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/plugin/agents.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/plugin/agents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 内置 Cabbage 引擎运行时(阶段二:先内嵌 Python,后续再评估 TS 移植)。
3
+ *
4
+ * 插件包内置 `vendor/cabbage/cabbage_cli`(由 scripts/sync-cabbage.mjs 固定版本生成),
5
+ * 首次 setup 时按插件版本释放到 `~/.config/opencode/cabbage/cli@<version>/`,
6
+ * 之后所有工具调用以 `python3 -m cabbage_cli` 执行(PYTHONPATH 指向该目录)。
7
+ * 需要 Python 3.10+;缺失时 available=false 且给出原因,工具层返回可读错误。
8
+ */
9
+ export interface CabbageCliRuntime {
10
+ available: boolean;
11
+ python: string | null;
12
+ dir: string | null;
13
+ version: string | null;
14
+ reason: string | null;
15
+ }
16
+ export interface CabbageRunResult {
17
+ code: number;
18
+ stdout: string;
19
+ stderr: string;
20
+ }
21
+ /** 探测可用的 Python 解释器(python3 优先);均不可用返回 null。 */
22
+ export declare function detectPython(): Promise<string | null>;
23
+ export declare function setupCabbageCli(packageRoot: string, version: string): Promise<CabbageCliRuntime>;
24
+ /**
25
+ * 执行一次 cabbage 子命令。非零退出以结果返回(code/stdout/stderr),
26
+ * 仅进程无法启动(如解释器消失)才抛错——调用方按 CLI 语义处理退出码。
27
+ */
28
+ export declare function runCabbage(cli: CabbageCliRuntime, args: string[], cwd: string): Promise<CabbageRunResult>;
@@ -0,0 +1,92 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { ensureInstalledDir } from "./install-dir.js";
7
+ const execFileAsync = promisify(execFile);
8
+ /** CLI 释放目录(按插件版本隔离);env CABBAGE_CLI_DIR 可覆盖,方便测试。 */
9
+ function resolveCliDir(version) {
10
+ if (process.env.CABBAGE_CLI_DIR) {
11
+ return path.join(process.env.CABBAGE_CLI_DIR, `cli@${version}`);
12
+ }
13
+ return path.join(homedir(), ".config", "opencode", "cabbage", `cli@${version}`);
14
+ }
15
+ /** 探测可用的 Python 解释器(python3 优先);均不可用返回 null。 */
16
+ export async function detectPython() {
17
+ for (const candidate of ["python3", "python"]) {
18
+ try {
19
+ await execFileAsync(candidate, ["--version"], { timeout: 5_000 });
20
+ return candidate;
21
+ }
22
+ catch {
23
+ // 尝试下一个候选
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+ function errorMessage(err) {
29
+ return err?.message ?? String(err);
30
+ }
31
+ export async function setupCabbageCli(packageRoot, version) {
32
+ const sourceDir = path.join(packageRoot, "vendor", "cabbage", "cabbage_cli");
33
+ const python = await detectPython();
34
+ if (!python) {
35
+ return {
36
+ available: false,
37
+ python: null,
38
+ dir: null,
39
+ version: null,
40
+ reason: "Python 3.10+ not found on PATH; install Python or the bundled cabbage engine cannot run",
41
+ };
42
+ }
43
+ const dir = resolveCliDir(version);
44
+ try {
45
+ // Python 包必须位于 <dir>/cabbage_cli(PYTHONPATH=<dir> 时可按包名导入)
46
+ await ensureInstalledDir({
47
+ sourceDir,
48
+ destDir: path.join(dir, "cabbage_cli"),
49
+ isComplete: async (d) => existsSync(path.join(d, "__main__.py")),
50
+ });
51
+ }
52
+ catch (err) {
53
+ return { available: false, python, dir: null, version: null, reason: `failed to install the bundled cabbage CLI: ${errorMessage(err)}` };
54
+ }
55
+ try {
56
+ const { stdout } = await execFileAsync(python, ["-m", "cabbage_cli", "--version"], {
57
+ cwd: dir,
58
+ env: { ...process.env, PYTHONPATH: dir },
59
+ timeout: 15_000,
60
+ });
61
+ return { available: true, python, dir, version: stdout.trim(), reason: null };
62
+ }
63
+ catch (err) {
64
+ return { available: false, python, dir, version: null, reason: `bundled cabbage CLI failed to run: ${errorMessage(err)}` };
65
+ }
66
+ }
67
+ /**
68
+ * 执行一次 cabbage 子命令。非零退出以结果返回(code/stdout/stderr),
69
+ * 仅进程无法启动(如解释器消失)才抛错——调用方按 CLI 语义处理退出码。
70
+ */
71
+ export async function runCabbage(cli, args, cwd) {
72
+ if (!cli.available || !cli.python || !cli.dir) {
73
+ throw new Error(cli.reason ?? "cabbage CLI unavailable");
74
+ }
75
+ try {
76
+ const { stdout, stderr } = await execFileAsync(cli.python, ["-m", "cabbage_cli", ...args], {
77
+ cwd,
78
+ env: { ...process.env, PYTHONPATH: cli.dir },
79
+ timeout: 120_000,
80
+ maxBuffer: 4 * 1024 * 1024,
81
+ });
82
+ return { code: 0, stdout: stdout.toString(), stderr: stderr.toString() };
83
+ }
84
+ catch (err) {
85
+ const e = err;
86
+ if (typeof e.code === "number") {
87
+ return { code: e.code, stdout: String(e.stdout ?? ""), stderr: String(e.stderr ?? "") };
88
+ }
89
+ throw err;
90
+ }
91
+ }
92
+ //# sourceMappingURL=cabbage-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cabbage-cli.js","sourceRoot":"","sources":["../../src/plugin/cabbage-cli.ts"],"names":[],"mappings":"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"}