@ghyper9023/pi-dev-workflow 0.6.2 → 0.7.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.
- package/.github/workflows/release.yml +56 -0
- package/.pre-commit-config.yaml +75 -0
- package/.version/RELEASE-v0.7.0.md +85 -0
- package/README.md +80 -228
- package/extensions/dev-prompts.ts +351 -450
- package/extensions/git-commands.ts +62 -171
- package/extensions/grill-me-agent.ts +98 -137
- package/extensions/session-utils.ts +169 -0
- package/extensions/ui-helpers.ts +2 -780
- package/package.json +2 -2
- package/prompts/APPEND_SYSTEM.md +63 -59
- package/skills/review-html/SKILL.md +1 -1
- package/tests/test-no-subagents.mjs +146 -0
- package/.doc/AGENT-FRONTMATTER-REFERENCE.md +0 -198
- package/agents/git-agent.md +0 -44
- package/agents/grill/dev-doc-grill-agent.md +0 -42
- package/agents/grill/dev-fix-grill-agent.md +0 -44
- package/agents/grill/dev-grill-agent.md +0 -40
- package/agents/grill/dev-perf-grill-agent.md +0 -45
- package/agents/grill/dev-prd-agent.md +0 -60
- package/agents/grill/dev-refactor-grill-agent.md +0 -46
- package/agents/grill/dev-test-grill-agent.md +0 -45
- package/agents/review-agent.md +0 -53
- package/agents/workflow/docWriter-agent.md +0 -53
- package/agents/workflow/planner-agent.md +0 -131
- package/agents/workflow/reviewer-agent.md +0 -128
- package/agents/workflow/trimmer-agent.md +0 -78
- package/agents/workflow/worker-agent.md +0 -70
- package/extensions/sub-agents.ts +0 -954
- package/extensions/workflow-engine.ts +0 -2005
- package/tests/test-grill-json-fix.mjs +0 -243
- package/tests/test-loopcount-timeout-fix.mjs +0 -336
- package/tests/test-output-directory-structure.mjs +0 -177
- package/tests/test-save-answer-file-workflow.mjs +0 -187
- package/tests/test-workflow-config.mjs +0 -244
- package/tests/test-workflow-engine-bugs.mjs +0 -908
- package/tests/test-workflow-engine.mjs +0 -518
|
@@ -1,224 +1,115 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Git Commands Extension
|
|
3
3
|
*
|
|
4
|
-
* Registers three commands
|
|
5
|
-
* /git-commit [message] -
|
|
4
|
+
* Registers three commands:
|
|
5
|
+
* /git-commit [message] - Commit changes
|
|
6
6
|
* /git-push - Push commits to remote
|
|
7
|
-
* /git-commit-push [message] -
|
|
7
|
+
* /git-commit-push [message] - Commit and push in one go
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* 传入消息时快速执行:暂存全部 → 提交/推送。
|
|
10
|
+
* 不传消息时把任务交给当前代理:由其查看 diff、识别变更并按
|
|
11
|
+
* Conventional Commits 规范分批提交(提交信息用中文)。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { discoverAgents, spawnSubagent, extractFinalOutput, type AgentDef } from "./sub-agents";
|
|
14
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
16
15
|
import { uiInput } from "./ui-helpers";
|
|
17
16
|
|
|
18
|
-
// ──
|
|
19
|
-
|
|
20
|
-
function getAgent(
|
|
21
|
-
ctx: { ui: { notify: (msg: string, type: string) => void } },
|
|
22
|
-
agent: AgentDef | undefined,
|
|
23
|
-
name: string,
|
|
24
|
-
): AgentDef | null {
|
|
25
|
-
if (!agent) {
|
|
26
|
-
ctx.ui.notify(`❌ ${name} not found (check agents/${name}.md)`, "error");
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
return agent;
|
|
30
|
-
}
|
|
17
|
+
// ── 委托给当前代理的任务描述 ─────────────────────────────────
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
status
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const result: GitSummary = { status: "unknown", summary: "", details: [] };
|
|
41
|
-
|
|
42
|
-
// Parse <status>...</status>
|
|
43
|
-
const statusMatch = output.match(/<status>([^<]*)<\/status>/);
|
|
44
|
-
if (statusMatch) {
|
|
45
|
-
const s = statusMatch[1].trim();
|
|
46
|
-
if (s.includes("✅") || s.includes("✔")) result.status = "success";
|
|
47
|
-
else if (s.includes("❌") || s.includes("✖")) result.status = "fail";
|
|
48
|
-
}
|
|
19
|
+
const COMMIT_BATCH_TASK = [
|
|
20
|
+
"请帮我完成这次 git 提交:",
|
|
21
|
+
"1. 执行 git status 与 git diff 查看当前变更,识别变更内容,必要时先用 git add 暂存相关文件。",
|
|
22
|
+
"2. 按 Conventional Commits 规范分批提交:把逻辑相关的改动拆成独立 commit,避免无关改动混入同一 commit;提交信息描述使用中文。",
|
|
23
|
+
"3. 直接执行提交,不需要向我确认。",
|
|
24
|
+
"4. 严谨修改任何代码,只做 git commit 工作。",
|
|
25
|
+
"5. 你的任务:识别 diff -> 分批提交;请迅速完成,不要进行额外的无关任务思考。"
|
|
26
|
+
].join("\n");
|
|
49
27
|
|
|
50
|
-
|
|
51
|
-
// Anchor to start of line (^ with m flag) to avoid matching a nested
|
|
52
|
-
// <summary> tag inside <details> content before the real top-level summary.
|
|
53
|
-
const summaryMatch = output.match(/^<summary>([\s\S]*?)<\/summary>/m);
|
|
54
|
-
if (summaryMatch) result.summary = summaryMatch[1].trim();
|
|
28
|
+
const COMMIT_PUSH_TASK = COMMIT_BATCH_TASK + "\n6. 全部提交完成后执行 git push 推送。";
|
|
55
29
|
|
|
56
|
-
|
|
57
|
-
// Anchor to start of line to avoid matching nested <details> inside content.
|
|
58
|
-
const detailsMatch = output.match(/^<details>([\s\S]*?)<\/details>/m);
|
|
59
|
-
if (detailsMatch) {
|
|
60
|
-
const lines = detailsMatch[1]
|
|
61
|
-
.split("\n")
|
|
62
|
-
.map((l) => l.replace(/^[-*]\s*/, "").trim())
|
|
63
|
-
.filter(Boolean);
|
|
64
|
-
result.details = lines;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Fallback: if no structured format, use raw lines
|
|
68
|
-
if (!statusMatch && !summaryMatch) {
|
|
69
|
-
const lines = output.split("\n").filter((l) => l.trim());
|
|
70
|
-
result.summary = lines[0] || "";
|
|
71
|
-
result.details = lines.slice(1).map((l) => l.replace(/^[-*]\s*/, "").trim()).filter(Boolean);
|
|
72
|
-
// Infer status from content
|
|
73
|
-
if (output.includes("✅") || output.includes("success") || output.includes("完成")) {
|
|
74
|
-
result.status = "success";
|
|
75
|
-
} else if (output.includes("❌") || output.includes("fail") || output.includes("失败")) {
|
|
76
|
-
result.status = "fail";
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Enrich a detail line with relevant emoji icons. */
|
|
84
|
-
function iconify(line: string): string {
|
|
85
|
-
const lc = line.toLowerCase();
|
|
86
|
-
if (lc.startsWith("commit")) return `📝 ${line}`;
|
|
87
|
-
if (lc.startsWith("push")) return `📤 ${line}`;
|
|
88
|
-
if (lc.includes("file") || lc.includes("文件")) return `📁 ${line}`;
|
|
89
|
-
if (lc.startsWith("branch") || lc.includes("branch")) return `🌿 ${line}`;
|
|
90
|
-
if (lc.startsWith("tag") || lc.includes("tag")) return `🏷️ ${line}`;
|
|
91
|
-
return ` ${line}`; // indent others
|
|
92
|
-
}
|
|
30
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
93
31
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
32
|
+
/** Run a git command through pi's executor and report the outcome. */
|
|
33
|
+
async function runGitCommand(
|
|
34
|
+
pi: ExtensionAPI,
|
|
97
35
|
ctx: {
|
|
98
36
|
cwd: string;
|
|
99
|
-
|
|
100
|
-
ui: {
|
|
101
|
-
setStatus: (key: string, status: string | undefined) => void;
|
|
102
|
-
notify: (msg: string, type: string) => void;
|
|
103
|
-
};
|
|
37
|
+
ui: { notify: (msg: string, type: string) => void };
|
|
104
38
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
39
|
+
args: string[],
|
|
40
|
+
action: string,
|
|
41
|
+
): Promise<boolean> {
|
|
109
42
|
try {
|
|
110
|
-
const result = await
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
ctx.
|
|
114
|
-
|
|
115
|
-
undefined, // use agent's default timeout
|
|
116
|
-
(progress) => {
|
|
117
|
-
ctx.ui.setStatus("subagent", progress.slice(0, 50));
|
|
118
|
-
},
|
|
119
|
-
);
|
|
120
|
-
const dur = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
121
|
-
|
|
122
|
-
// Extract output with fallback to raw stdout
|
|
123
|
-
let output = extractFinalOutput(result.output);
|
|
124
|
-
if (!output && result.output.trim()) {
|
|
125
|
-
const lines = result.output.split("\n").filter((l) => {
|
|
126
|
-
const t = l.trim();
|
|
127
|
-
return t && !t.startsWith("{") && !t.startsWith("[");
|
|
128
|
-
});
|
|
129
|
-
if (lines.length > 0) {
|
|
130
|
-
output = lines.join("\n").trim();
|
|
131
|
-
}
|
|
43
|
+
const result = await pi.exec("git", args, { cwd: ctx.cwd, timeout: 120_000 });
|
|
44
|
+
if (result.code !== 0) {
|
|
45
|
+
const detail = result.stderr?.trim() || result.stdout?.trim() || "未知错误";
|
|
46
|
+
ctx.ui.notify(`❌ ${action} 失败 (exit ${result.code}): ${detail}`, "error");
|
|
47
|
+
return false;
|
|
132
48
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const statusIcon = parsed.status === "success" ? "✅" : parsed.status === "fail" ? "❌" : "ℹ️";
|
|
139
|
-
const detailText = parsed.details.length > 0 ? ` | ${parsed.details.join(" | ")}` : "";
|
|
140
|
-
const msg = `${statusIcon} ${parsed.summary || "done"} (${dur}s)${detailText}`;
|
|
141
|
-
const notifyType = parsed.status === "fail" ? "error" : "success";
|
|
142
|
-
ctx.ui.notify(msg, notifyType);
|
|
143
|
-
} else if (result.exitCode !== 0) {
|
|
144
|
-
const errMsg = result.stderr || result.output.slice(0, 300) || "未知错误";
|
|
145
|
-
ctx.ui.notify(`❌ git-sub-agent 失败 (${dur}s): ${errMsg}`, "error");
|
|
146
|
-
} else {
|
|
147
|
-
ctx.ui.notify(`✅ git-sub-agent 完成 (${dur}s)`, "success");
|
|
148
|
-
}
|
|
149
|
-
} finally {
|
|
150
|
-
ctx.ui.setStatus("subagent", undefined);
|
|
49
|
+
ctx.ui.notify(`✅ ${action} 完成`, "success");
|
|
50
|
+
return true;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
ctx.ui.notify(`❌ ${action} 异常: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
53
|
+
return false;
|
|
151
54
|
}
|
|
152
55
|
}
|
|
153
56
|
|
|
154
57
|
// ── Extension ────────────────────────────────────────────────
|
|
155
58
|
|
|
156
59
|
export default function (pi: ExtensionAPI) {
|
|
157
|
-
const agents = discoverAgents();
|
|
158
|
-
const gitAgent = agents.find((a) => a.name === "git-agent");
|
|
159
|
-
|
|
160
60
|
// ── /git-commit ────────────────────────────────────────────
|
|
161
61
|
pi.registerCommand("git-commit", {
|
|
162
|
-
description: "(
|
|
62
|
+
description: "Commit changes (leave message empty to let the agent review the diff and commit in batches)",
|
|
163
63
|
handler: async (args, ctx) => {
|
|
164
|
-
const agent = getAgent(ctx, gitAgent, "git-agent");
|
|
165
|
-
if (!agent) return;
|
|
166
|
-
|
|
167
64
|
let message = args.trim();
|
|
168
65
|
if (!message) {
|
|
169
|
-
const input = await uiInput(ctx, "Commit message", "
|
|
170
|
-
if (input === undefined)
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
66
|
+
const input = await uiInput(ctx, "Commit message", "直接回车将交由主代理识别变更并分批提交,或输入消息直接提交...");
|
|
67
|
+
if (input === undefined) return;
|
|
173
68
|
message = input.trim();
|
|
174
69
|
}
|
|
175
70
|
|
|
71
|
+
if (message) {
|
|
72
|
+
const addOk = await runGitCommand(pi, ctx, ["add", "-A"], "暂存所有变更");
|
|
73
|
+
if (!addOk) return;
|
|
74
|
+
await runGitCommand(pi, ctx, ["commit", "-m", message], `提交 (${message})`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
176
77
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
: `Stage all changes with git add -A, check the diff, write a Conventional Commits message, and commit. Do NOT ask for confirmation.`;
|
|
180
|
-
|
|
181
|
-
await runSubAgent(agent, task, ctx);
|
|
78
|
+
ctx.ui.notify("🔄 已交由当前代理识别变更并分批提交...", "info");
|
|
79
|
+
pi.sendUserMessage(COMMIT_BATCH_TASK, { deliverAs: "followUp" });
|
|
182
80
|
},
|
|
183
81
|
});
|
|
184
82
|
|
|
185
83
|
// ── /git-push ─────────────────────────────────────────────
|
|
186
84
|
pi.registerCommand("git-push", {
|
|
187
|
-
description: "
|
|
85
|
+
description: "Push commits to the remote repository",
|
|
188
86
|
handler: async (_args, ctx) => {
|
|
189
|
-
|
|
190
|
-
if (!agent) return;
|
|
191
|
-
|
|
192
|
-
await runSubAgent(
|
|
193
|
-
agent,
|
|
194
|
-
"Push commits to remote with git push. Do NOT ask for confirmation.",
|
|
195
|
-
ctx,
|
|
196
|
-
);
|
|
87
|
+
await runGitCommand(pi, ctx, ["push"], "推送到远程");
|
|
197
88
|
},
|
|
198
89
|
});
|
|
199
90
|
|
|
200
91
|
// ── /git-commit-push ──────────────────────────────────────
|
|
201
92
|
pi.registerCommand("git-commit-push", {
|
|
202
|
-
description: "(
|
|
93
|
+
description: "Commit and push in one go (leave message empty to let the agent review the diff and commit in batches)",
|
|
203
94
|
handler: async (args, ctx) => {
|
|
204
|
-
const agent = getAgent(ctx, gitAgent, "git-agent");
|
|
205
|
-
if (!agent) return;
|
|
206
|
-
|
|
207
95
|
let message = args.trim();
|
|
208
96
|
if (!message) {
|
|
209
|
-
const input = await uiInput(ctx, "Commit message", "
|
|
210
|
-
if (input === undefined)
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
97
|
+
const input = await uiInput(ctx, "Commit message", "直接回车将交由主代理识别变更、分批提交并推送,或输入消息直接提交并推送...");
|
|
98
|
+
if (input === undefined) return;
|
|
213
99
|
message = input.trim();
|
|
214
100
|
}
|
|
215
101
|
|
|
102
|
+
if (message) {
|
|
103
|
+
const addOk = await runGitCommand(pi, ctx, ["add", "-A"], "暂存所有变更");
|
|
104
|
+
if (!addOk) return;
|
|
105
|
+
const commitOk = await runGitCommand(pi, ctx, ["commit", "-m", message], `提交 (${message})`);
|
|
106
|
+
if (!commitOk) return;
|
|
107
|
+
await runGitCommand(pi, ctx, ["push"], "推送到远程");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
216
110
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
: `Stage all changes with git add -A, check the diff, write a Conventional Commits message, commit, then push. Do NOT ask for confirmation.`;
|
|
220
|
-
|
|
221
|
-
await runSubAgent(agent, task, ctx);
|
|
111
|
+
ctx.ui.notify("🔄 已交由当前代理识别变更、分批提交并推送...", "info");
|
|
112
|
+
pi.sendUserMessage(COMMIT_PUSH_TASK, { deliverAs: "followUp" });
|
|
222
113
|
},
|
|
223
114
|
});
|
|
224
|
-
}
|
|
115
|
+
}
|