Skip to content

Commit 8dedd5d

Browse files
authored
fix: repair worker guard cwd handling and Codex role wording
Merge PR #41 to close P1 issues #15 and #19.
2 parents 10094c2 + bc069b8 commit 8dedd5d

15 files changed

Lines changed: 238 additions & 57 deletions

.codex-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "devflow-skills",
3-
"version": "0.2.11",
3+
"version": "0.2.12",
44
"description": "DevFlow engineering workflow skills for Codex: routing, DDD, TDD, planning, execution, debugging, review, verification, and branch finishing.",
55
"composerIcon": "./assets/app-icon-small.svg",
66
"logo": "./assets/app-icon.png",
@@ -24,7 +24,7 @@
2424
"websiteURL": "https://github.com/LiTeXz/devflow-skills",
2525
"defaultPrompt": [
2626
"Use df-engineering-workflow-router to classify this task",
27-
"Use `df-publisher`subagent, management this git and github.",
27+
"Use the df-publisher Codex worker session for git and GitHub publishing tasks.",
2828
"Create a DDD to TDD implementation plan",
2929
"Verify this branch before completion"
3030
],

.github/workflows/version-check.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,11 @@ jobs:
7777
7878
while IFS= read -r path; do
7979
mkdir -p "$TMP_DIR/$(dirname "$path")"
80-
url="https://raw.githubusercontent.com/${REPO}/${TAG}/${path}"
81-
if ! curl --fail --show-error --location "$url" --output "$TMP_DIR/$path"; then
80+
url="https://api.github.com/repos/${REPO}/contents/${path}?ref=${TAG}"
81+
if ! curl --fail --show-error --location --retry 5 --retry-all-errors --retry-delay 2 \
82+
--header "Accept: application/vnd.github.raw" \
83+
--header "Authorization: Bearer ${{ github.token }}" \
84+
"$url" --output "$TMP_DIR/$path"; then
8285
echo "::error::Missing managed asset on ${TAG}: ${path}"
8386
exit 1
8487
fi

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# DevFlow Skills
22

3-
DevFlow Skills 是一组面向 Codex 和 OpenCode 的工程工作流 skills。它把 DDD、Glue Coding、TDD、计划、执行、调试、评审、验证和分支收尾组织成一套可组合的强制工作流。
3+
DevFlow Skills 是一组面向 Codex 的工程工作流 skills,并提供 OpenCode 兼容适配。它把 DDD、Glue Coding、TDD、计划、执行、调试、评审、验证和分支收尾组织成一套可组合的强制工作流。
44

55
插件还包含一个保护分支 hook:在 `main``dev``develop``devlop`
66
等常见集成分支上启动会话时提醒切换新分支;在这些分支上拦截写文件、
77
修改索引、提交、重置、依赖安装和直接 push 等污染操作,并提示改用新分支
88
和 PR 流程。Codex hook 配置位于 `hooks/hooks.codex.json`,并由
99
`.codex-plugin/plugin.json` 声明为插件 companion;OpenCode 本地插件入口位于 `.opencode/plugin/devflow-skills.ts`
10-
另有主 Agent 禁写 hook:所有分支上主 Agent 只能协调、审查和验证,代码写入必须交给 worker/subagent 或 OpenCode subagent
10+
另有 main Codex session 禁写 hook:所有分支上 main Codex session 只能协调、审查和验证,代码写入必须交给 Codex worker session 或兼容适配器识别的写入会话
1111

1212
## 重要:必须开启 Plan 模式
1313

@@ -84,7 +84,7 @@ OpenCode 当前按本地 checkout 使用。仓库已提供 `.opencode/plugin/dev
8484
}
8585
```
8686

87-
OpenCode 插件会复用同一套保护规则:主 Agent 禁写、subagent 可在非保护分支写入、保护分支写入拦截、所有 agent 的 `git push` 全局拦截。修改 `opencode.json`、插件或 skills 后,需要退出并重启 OpenCode 才会重新加载。
87+
OpenCode 插件会复用同一套保护规则:main Codex session 禁写、兼容适配器识别的写入会话可在非保护分支写入、保护分支写入拦截、所有 agent 的 `git push` 全局拦截。修改 `opencode.json`、插件或 skills 后,需要退出并重启 OpenCode 才会重新加载。
8888

8989
## 技能组合图
9090

agents/df-publisher.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
# devflow-version = "0.2.11"
1+
# devflow-version = "0.2.12"
22
name = "df-publisher"
3-
description = "唯一被授权执行 git 和 gh 命令的子代理。负责提交、推送、PR、合并、发布等全部 git/github 操作。遵守保护分支规则。"
3+
description = "唯一被授权执行 git 和 gh 命令的 Codex worker session。负责提交、推送、PR、合并、发布等全部 git/github 操作。遵守保护分支规则。"
44
nickname_candidates = ["df-publisher", "publisher"]
55

66
developer_instructions = """
77
## 角色定义
88
9-
你是 df-publisher,DevFlow 插件中唯一被授权执行 git 和 gh 命令的子代理
10-
所有其他 Agent(主 Agent 和 worker)均被禁止执行任何 git/gh 命令,必须委托给你完成。
9+
你是 df-publisher,DevFlow 插件中唯一被授权执行 git 和 gh 命令的 Codex worker session
10+
所有其他会话(main Codex session 和普通 Codex worker session)均被禁止执行发布类 git/gh 命令,必须委托给你完成。
1111
1212
## 职责范围
1313

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "devflow-skills",
3-
"version": "0.2.11",
3+
"version": "0.2.12",
44
"type": "module",
55
"private": true,
66
"scripts": {

scripts/opencode-adapter.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe("OpenCode adapter", () => {
5050
});
5151

5252
expect(decision).not.toBeNull();
53-
expect(decision?.reason).toInclude("主 Agent 禁止写入");
53+
expect(decision?.reason).toInclude("main Codex session 禁止写入");
5454
});
5555

5656
it("blocks main agent shell writes", () => {
@@ -62,10 +62,10 @@ describe("OpenCode adapter", () => {
6262
});
6363

6464
expect(decision).not.toBeNull();
65-
expect(decision?.reason).toInclude("主 Agent 禁止写入");
65+
expect(decision?.reason).toInclude("main Codex session 禁止写入");
6666
});
6767

68-
it("allows subagent writes on feature branches", () => {
68+
it("allows compatible worker writes on feature branches", () => {
6969
expect(
7070
shouldBlockOpenCodeToolInput({
7171
tool: "write",
@@ -76,7 +76,7 @@ describe("OpenCode adapter", () => {
7676
).toBeUndefined();
7777
});
7878

79-
it("blocks subagent writes on protected branches", () => {
79+
it("blocks compatible worker writes on protected branches", () => {
8080
const decision = shouldBlockOpenCodeToolInput({
8181
tool: "write",
8282
args: { filePath: "README.md" },

scripts/prevent-git-github-operations.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,14 @@ export function ensureDfPublisherAgent(
212212
if (reinstallReason) {
213213
return `DevFlow: ${reinstallReason}至 v${expectedVersion}${dfPublisherToml})`;
214214
}
215-
return `DevFlow: df-publisher 子代理定义已更新至 v${expectedVersion}${dfPublisherToml})`;
215+
return `DevFlow: df-publisher Codex worker session 定义已更新至 v${expectedVersion}${dfPublisherToml})`;
216216
}
217-
return `DevFlow: 已自动安装 df-publisher 子代理定义到 ${dfPublisherToml}`;
217+
return `DevFlow: 已自动安装 df-publisher Codex worker session 定义到 ${dfPublisherToml}`;
218218
}
219219
}
220220

221221
const lines = [
222-
"DevFlow 插件不完整:未找到 df-publisher 子代理定义。",
222+
"DevFlow 插件不完整:未找到 df-publisher Codex worker session 定义。",
223223
`预期位置:${dfPublisherToml}`,
224224
];
225225
if (pluginRoot) {
@@ -249,7 +249,7 @@ function invalidSourceMessage(
249249
reason: string,
250250
): string {
251251
return [
252-
"DevFlow 插件不完整:df-publisher 子代理源文件无效。",
252+
"DevFlow 插件不完整:df-publisher Codex worker session 源文件无效。",
253253
`源文件:${sourceToml}`,
254254
`原因:${reason}`,
255255
`目标位置:${dfPublisherToml}`,
@@ -283,10 +283,10 @@ function writeSessionStartMessage(message: string): void {
283283
function writeToolBlock(): void {
284284
const lines = [
285285
"DevFlow 已阻止 git/gh 发布操作。",
286-
"原因:仅 df-publisher 子代理可执行 git push、git commit、gh issue、gh pr。",
286+
"原因:仅 df-publisher Codex worker session 可执行 git push、git commit、gh issue、gh pr。",
287287
"",
288-
"主 Agent 和其他 worker 可直接执行简单 git/gh 操作(如切换分支、合并、认证),",
289-
"但提交、推送、PR、issue 管理必须委托 df-publisher 子代理完成。",
288+
"main Codex session 和普通 Codex worker session 可直接执行简单 git/gh 操作(如切换分支、合并、认证),",
289+
"但提交、推送、PR、issue 管理必须委托 df-publisher Codex worker session 完成。",
290290
];
291291
for (const line of lines) {
292292
process.stderr.write(`${line}\n`);

scripts/prevent-main-agent-write.test.ts

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ function switchBranch(path: string, branch: string): void {
4444
});
4545
}
4646

47+
function initBranchOnlyRepo(path: string, branch: string): void {
48+
mkdirSync(path, { recursive: true });
49+
Bun.spawnSync({
50+
cmd: ["git", "init", "-b", branch],
51+
cwd: path,
52+
stdout: "ignore",
53+
});
54+
}
55+
4756
// Set up state path for tests
4857
let stateFilePath: string;
4958
const STATE_PATH_ENV = "DEVFLOW_MAIN_AGENT_WRITE_STATE";
@@ -242,8 +251,8 @@ describe("MainAgentWriteGuard", () => {
242251
});
243252
});
244253

245-
describe("subagent sessions", () => {
246-
it("blocks git push even for registered subagent", () => {
254+
describe("Codex worker sessions", () => {
255+
it("blocks git push even for registered Codex worker", () => {
247256
startSubagent("worker-1");
248257
expect(
249258
shouldBlockTool(
@@ -268,15 +277,15 @@ describe("MainAgentWriteGuard", () => {
268277
).not.toBeNull();
269278
});
270279

271-
it("allows registered subagent write", () => {
280+
it("allows registered Codex worker write", () => {
272281
startSubagent("worker-1");
273282
expect(shouldBlockTool("Write", {}, "worker-1")).toBeUndefined();
274283
expect(
275284
shouldBlockTool("Bash", { command: "git add README.md" }, "worker-1"),
276285
).toBeUndefined();
277286
});
278287

279-
it("blocks after subagent stop", () => {
288+
it("blocks after Codex worker stop", () => {
280289
startSubagent("worker-1");
281290
stopSubagent("worker-1");
282291
expect(shouldBlockTool("Write", {}, "worker-1")).not.toBeNull();
@@ -289,7 +298,7 @@ describe("MainAgentWriteGuard", () => {
289298
).not.toBeNull();
290299
});
291300

292-
it("multiple subagent sessions do not interfere", () => {
301+
it("multiple Codex worker sessions do not interfere", () => {
293302
startSubagent("worker-1");
294303
startSubagent("worker-2");
295304
expect(shouldBlockTool("Write", {}, "worker-1")).toBeUndefined();
@@ -313,7 +322,7 @@ describe("MainAgentWriteGuard", () => {
313322
).toBeUndefined();
314323
});
315324

316-
it("allows unregistered shell write when nested agent declares subagent worker context", () => {
325+
it("allows unregistered shell write when adapter payload declares writable worker context", () => {
317326
expect(
318327
shouldBlockTool(
319328
"Bash",
@@ -418,8 +427,8 @@ describe("MainAgentWriteGuard", () => {
418427
});
419428
});
420429

421-
describe("protected branch write for registered subagent", () => {
422-
it("blocks registered subagent write on protected branch via workdir", () => {
430+
describe("protected branch write for registered Codex worker", () => {
431+
it("blocks registered Codex worker write on protected branch via workdir", () => {
423432
startSubagent("worker-1");
424433

425434
const root = join(tempDir, "pb-root");
@@ -452,6 +461,36 @@ describe("MainAgentWriteGuard", () => {
452461
expect(decision?.reason).toInclude("cwd");
453462
expect(decision?.reason).toInclude(root);
454463
});
464+
465+
it("uses git -C cwd instead of parent cwd for registered worker git writes", () => {
466+
startSubagent("worker-1");
467+
468+
const root = join(tempDir, "worker-nested-git-c-root");
469+
const allowedNested = join(root, "allowed-nested");
470+
const blockedNested = join(root, "blocked-nested");
471+
initBranchOnlyRepo(root, "dev");
472+
initBranchOnlyRepo(allowedNested, "codex/topic");
473+
initBranchOnlyRepo(blockedNested, "dev");
474+
475+
expect(
476+
shouldBlockTool(
477+
"Bash",
478+
{ command: `git -C "${allowedNested}" add README.md` },
479+
"worker-1",
480+
root,
481+
),
482+
).toBeUndefined();
483+
484+
const decision = shouldBlockTool(
485+
"Bash",
486+
{ command: `git -C "${blockedNested}" add README.md` },
487+
"worker-1",
488+
root,
489+
);
490+
expect(decision).not.toBeNull();
491+
expect(decision?.reason).toInclude("cwd");
492+
expect(decision?.reason).toInclude(blockedNested);
493+
});
455494
});
456495

457496
describe("hook manifest", () => {

0 commit comments

Comments
 (0)