@fitlab-ai/agent-infra 0.5.7 → 0.5.9

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 (132) hide show
  1. package/README.md +44 -4
  2. package/README.zh-CN.md +44 -4
  3. package/lib/defaults.json +4 -2
  4. package/lib/init.js +18 -1
  5. package/lib/sandbox/commands/vm.js +7 -1
  6. package/lib/sandbox/constants.js +3 -0
  7. package/lib/sandbox/engine.js +57 -3
  8. package/lib/sandbox/runtimes/base.dockerfile +9 -2
  9. package/lib/sandbox/shell.js +36 -2
  10. package/lib/update.js +14 -3
  11. package/package.json +6 -4
  12. package/templates/.agents/QUICKSTART.en.md +2 -2
  13. package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
  14. package/templates/.agents/README.en.md +1 -1
  15. package/templates/.agents/README.zh-CN.md +1 -1
  16. package/templates/.agents/rules/create-issue.en.md +5 -0
  17. package/templates/.agents/rules/create-issue.github.en.md +178 -0
  18. package/templates/.agents/rules/create-issue.github.zh-CN.md +178 -0
  19. package/templates/.agents/rules/create-issue.zh-CN.md +5 -0
  20. package/templates/.agents/rules/issue-pr-commands.github.en.md +60 -0
  21. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +60 -0
  22. package/templates/.agents/rules/issue-sync.en.md +14 -0
  23. package/templates/.agents/rules/issue-sync.github.en.md +15 -1
  24. package/templates/.agents/rules/issue-sync.github.zh-CN.md +15 -1
  25. package/templates/.agents/rules/issue-sync.zh-CN.md +14 -0
  26. package/templates/.agents/rules/label-milestone-setup.github.en.md +10 -0
  27. package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +10 -0
  28. package/templates/.agents/rules/milestone-inference.github.en.md +2 -2
  29. package/templates/.agents/rules/milestone-inference.github.zh-CN.md +2 -2
  30. package/templates/.agents/rules/release-commands.github.en.md +16 -0
  31. package/templates/.agents/rules/release-commands.github.zh-CN.md +16 -0
  32. package/templates/.agents/scripts/platform-adapters/find-existing-task.github.js +272 -0
  33. package/templates/.agents/scripts/platform-adapters/find-existing-task.js +5 -0
  34. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +134 -9
  35. package/templates/.agents/scripts/platform-adapters/platform-sync.js +7 -0
  36. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  37. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  38. package/templates/.agents/skills/analyze-task/config/verify.json +3 -1
  39. package/templates/.agents/skills/block-task/config/verify.json +2 -1
  40. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  41. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  42. package/templates/.agents/skills/cancel-task/config/verify.json +2 -1
  43. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  44. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  45. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  46. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  47. package/templates/.agents/skills/commit/config/verify.json +2 -1
  48. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  49. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  50. package/templates/.agents/skills/complete-task/config/verify.json +2 -1
  51. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  52. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  53. package/templates/.agents/skills/create-pr/config/verify.json +2 -1
  54. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +7 -17
  55. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +27 -37
  56. package/templates/.agents/skills/create-release-note/SKILL.en.md +9 -9
  57. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +9 -9
  58. package/templates/.agents/skills/create-task/SKILL.en.md +70 -12
  59. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +71 -13
  60. package/templates/.agents/skills/create-task/config/verify.json +6 -1
  61. package/templates/.agents/skills/implement-task/SKILL.en.md +1 -1
  62. package/templates/.agents/skills/implement-task/SKILL.zh-CN.md +1 -1
  63. package/templates/.agents/skills/implement-task/config/verify.json +3 -1
  64. package/templates/.agents/skills/implement-task/reference/implementation-rules.en.md +7 -12
  65. package/templates/.agents/skills/implement-task/reference/implementation-rules.zh-CN.md +7 -12
  66. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  67. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  68. package/templates/.agents/skills/import-issue/SKILL.en.md +40 -10
  69. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +40 -10
  70. package/templates/.agents/skills/init-labels/SKILL.en.md +9 -9
  71. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -9
  72. package/templates/.agents/skills/init-milestones/SKILL.en.md +7 -7
  73. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +7 -7
  74. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  75. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  76. package/templates/.agents/skills/plan-task/config/verify.json +3 -1
  77. package/templates/.agents/skills/refine-task/SKILL.en.md +1 -1
  78. package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +1 -1
  79. package/templates/.agents/skills/refine-task/config/verify.json +3 -1
  80. package/templates/.agents/skills/refine-task/reference/fix-workflow.en.md +2 -2
  81. package/templates/.agents/skills/refine-task/reference/fix-workflow.zh-CN.md +2 -2
  82. package/templates/.agents/skills/restore-task/SKILL.en.md +13 -64
  83. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +13 -64
  84. package/templates/.agents/skills/review-task/SKILL.en.md +1 -1
  85. package/templates/.agents/skills/review-task/SKILL.zh-CN.md +1 -1
  86. package/templates/.agents/skills/review-task/config/verify.json +3 -1
  87. package/templates/.agents/skills/test/SKILL.en.md +45 -6
  88. package/templates/.agents/skills/test/SKILL.zh-CN.md +45 -6
  89. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -0
  90. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -0
  91. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +56 -5
  92. package/templates/.claude/commands/import-issue.en.md +1 -1
  93. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  94. package/templates/.claude/commands/init-labels.en.md +1 -1
  95. package/templates/.claude/commands/init-labels.zh-CN.md +1 -1
  96. package/templates/.claude/commands/init-milestones.en.md +1 -1
  97. package/templates/.claude/commands/init-milestones.zh-CN.md +1 -1
  98. package/templates/.claude/commands/restore-task.en.md +1 -1
  99. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  100. package/templates/.claude/hooks/check-version-format.sh +1 -1
  101. package/templates/.gemini/commands/_project_/import-issue.en.toml +1 -1
  102. package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +1 -1
  103. package/templates/.gemini/commands/_project_/init-labels.en.toml +2 -2
  104. package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +2 -2
  105. package/templates/.gemini/commands/_project_/init-milestones.en.toml +2 -2
  106. package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +2 -2
  107. package/templates/.gemini/commands/_project_/restore-task.en.toml +1 -1
  108. package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +1 -1
  109. package/templates/{.github/hooks → .git-hooks}/check-version-format.sh +2 -2
  110. package/templates/.github/workflows/pr-label.yml +1 -1
  111. package/templates/.opencode/commands/import-issue.en.md +1 -1
  112. package/templates/.opencode/commands/import-issue.zh-CN.md +1 -1
  113. package/templates/.opencode/commands/init-labels.en.md +1 -1
  114. package/templates/.opencode/commands/init-labels.zh-CN.md +1 -1
  115. package/templates/.opencode/commands/init-milestones.en.md +1 -1
  116. package/templates/.opencode/commands/init-milestones.zh-CN.md +1 -1
  117. package/templates/.opencode/commands/restore-task.en.md +1 -1
  118. package/templates/.opencode/commands/restore-task.zh-CN.md +1 -1
  119. package/templates/.agents/skills/create-issue/SKILL.en.md +0 -118
  120. package/templates/.agents/skills/create-issue/SKILL.zh-CN.md +0 -118
  121. package/templates/.agents/skills/create-issue/config/verify.json +0 -29
  122. package/templates/.agents/skills/create-issue/reference/label-and-type.en.md +0 -71
  123. package/templates/.agents/skills/create-issue/reference/label-and-type.zh-CN.md +0 -71
  124. package/templates/.agents/skills/create-issue/reference/template-matching.en.md +0 -45
  125. package/templates/.agents/skills/create-issue/reference/template-matching.zh-CN.md +0 -45
  126. package/templates/.claude/commands/create-issue.en.md +0 -8
  127. package/templates/.claude/commands/create-issue.zh-CN.md +0 -8
  128. package/templates/.gemini/commands/_project_/create-issue.en.toml +0 -8
  129. package/templates/.gemini/commands/_project_/create-issue.zh-CN.toml +0 -8
  130. package/templates/.opencode/commands/create-issue.en.md +0 -11
  131. package/templates/.opencode/commands/create-issue.zh-CN.md +0 -11
  132. /package/templates/{.github/hooks → .git-hooks}/pre-commit +0 -0
@@ -1,6 +1,6 @@
1
1
  # Milestone 推断规则
2
2
 
3
- 在 `create-issue`、`implement-task` 或 `create-pr` 处理 milestone 之前先读取本文件。
3
+ 在 `create-task` 的平台规则、`implement-task` 或 `create-pr` 处理 milestone 之前先读取本文件。
4
4
 
5
5
  ## 通用原则
6
6
 
@@ -21,7 +21,7 @@ git branch -r | grep -v 'HEAD' | grep -E 'origin/[0-9]+\.[0-9]+\.x$'
21
21
  - 有输出:多版本分支模式
22
22
  - 无输出:主干模式
23
23
 
24
- ## 阶段 1:`create-issue`
24
+ ## 阶段 1:`create-task`(平台规则创建 Issue 时)
25
25
 
26
26
  目标:在创建 Issue 时先确定粗粒度版本线。
27
27
 
@@ -21,6 +21,22 @@ When needed, read the linked Issue:
21
21
  gh issue view {issue-number} --json number,title,labels,url
22
22
  ```
23
23
 
24
+ ## Contributor Mapping Helpers
25
+
26
+ Merged PR queries used for release notes should include authors when contributors are needed:
27
+
28
+ ```bash
29
+ gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels,author
30
+ ```
31
+
32
+ Linked Issue queries used for reporter attribution should include the author:
33
+
34
+ ```bash
35
+ gh issue view {issue-number} --json number,title,labels,url,author
36
+ ```
37
+
38
+ Map GitHub no-reply emails with this rule: if `Name <email>` contains an email matching `(\d+\+)?(\S+?)@users\.noreply\.github\.com`, use the second capture group lowercased as the login. This covers both `{id}+{login}@users.noreply.github.com` and `{login}@users.noreply.github.com`.
39
+
24
40
  ## Create a Draft Release
25
41
 
26
42
  ```bash
@@ -21,6 +21,22 @@ gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels
21
21
  gh issue view {issue-number} --json number,title,labels,url
22
22
  ```
23
23
 
24
+ ## Contributor 映射辅助规则
25
+
26
+ release notes 需要 contributors 时,已合并 PR 查询应包含 author:
27
+
28
+ ```bash
29
+ gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels,author
30
+ ```
31
+
32
+ 关联 Issue 用于 reporter 归因时,查询应包含 author:
33
+
34
+ ```bash
35
+ gh issue view {issue-number} --json number,title,labels,url,author
36
+ ```
37
+
38
+ GitHub no-reply 邮箱映射规则:如果 `Name <email>` 中的 email 匹配 `(\d+\+)?(\S+?)@users\.noreply\.github\.com`,使用第二个捕获组的小写形式作为 login。该规则同时覆盖 `{id}+{login}@users.noreply.github.com` 和 `{login}@users.noreply.github.com`。
39
+
24
40
  ## 创建 Draft Release
25
41
 
26
42
  ```bash
@@ -0,0 +1,272 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { pathToFileURL } from "node:url";
3
+
4
+ const markerPattern = /^<!-- sync-issue:(TASK-\d{8}-\d{6}):([a-z][a-z0-9-]*) -->$/;
5
+
6
+ function parseArgs(argv) {
7
+ const args = {
8
+ format: "json"
9
+ };
10
+
11
+ for (let index = 0; index < argv.length; index += 1) {
12
+ const arg = argv[index];
13
+ if (arg === "--issue") {
14
+ args.issue = argv[index + 1];
15
+ index += 1;
16
+ continue;
17
+ }
18
+ if (arg === "--repo") {
19
+ args.repo = argv[index + 1];
20
+ index += 1;
21
+ continue;
22
+ }
23
+ if (arg === "--format") {
24
+ args.format = argv[index + 1];
25
+ index += 1;
26
+ continue;
27
+ }
28
+ if (arg === "-h" || arg === "--help") {
29
+ args.help = true;
30
+ continue;
31
+ }
32
+ throw new Error(`Unknown argument: ${arg}`);
33
+ }
34
+
35
+ return args;
36
+ }
37
+
38
+ function usage() {
39
+ return [
40
+ "Usage:",
41
+ " node .agents/scripts/platform-adapters/find-existing-task.js --issue <number> [--repo <owner/name>] [--format json]"
42
+ ].join("\n");
43
+ }
44
+
45
+ function runGh(args) {
46
+ const ghBin = process.env.IMPORT_ISSUE_GH_BIN || "gh";
47
+ const result = spawnSync(ghBin, args, {
48
+ encoding: "utf8",
49
+ maxBuffer: 10 * 1024 * 1024
50
+ });
51
+
52
+ if (result.error) {
53
+ const error = new Error(`gh command failed: ${result.error.message}`);
54
+ error.stderr = result.error.message;
55
+ throw error;
56
+ }
57
+
58
+ if (result.status !== 0) {
59
+ const stderr = result.stderr.trim() || `gh exited with status ${result.status}`;
60
+ const error = new Error(stderr);
61
+ error.stderr = stderr;
62
+ throw error;
63
+ }
64
+
65
+ return result.stdout;
66
+ }
67
+
68
+ function resolveRepo(explicitRepo) {
69
+ if (explicitRepo) {
70
+ return explicitRepo;
71
+ }
72
+
73
+ const currentRepo = runGh(["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"]).trim();
74
+ if (!currentRepo) {
75
+ throw new Error("Cannot detect current GitHub repository");
76
+ }
77
+
78
+ const upstreamRepo = runGh([
79
+ "api",
80
+ `repos/${currentRepo}`,
81
+ "--jq",
82
+ "if .fork then .parent.full_name else .full_name end"
83
+ ]).trim();
84
+
85
+ return upstreamRepo || currentRepo;
86
+ }
87
+
88
+ function parseComments(output) {
89
+ const trimmed = output.trim();
90
+ if (!trimmed) {
91
+ return [];
92
+ }
93
+
94
+ try {
95
+ const parsed = JSON.parse(trimmed);
96
+ return Array.isArray(parsed) ? parsed : [parsed];
97
+ } catch {
98
+ return trimmed
99
+ .split(/\r?\n/)
100
+ .filter(Boolean)
101
+ .map((line) => JSON.parse(line));
102
+ }
103
+ }
104
+
105
+ function fetchComments(repo, issueNumber) {
106
+ const output = runGh([
107
+ "api",
108
+ `repos/${repo}/issues/${issueNumber}/comments`,
109
+ "--paginate",
110
+ "--jq",
111
+ ".[] | @json"
112
+ ]);
113
+
114
+ return parseComments(output);
115
+ }
116
+
117
+ function firstLine(value) {
118
+ return String(value || "").split(/\r?\n/, 1)[0].trim();
119
+ }
120
+
121
+ function normalizeScalar(value) {
122
+ const trimmed = value.trim();
123
+ if (
124
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
125
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
126
+ ) {
127
+ return trimmed.slice(1, -1);
128
+ }
129
+ return trimmed;
130
+ }
131
+
132
+ function parseFrontmatterBlock(block) {
133
+ const match = block.match(/^---\r?\n([\s\S]*?)\r?\n---\s*$/);
134
+ if (!match) {
135
+ return null;
136
+ }
137
+
138
+ const metadata = {};
139
+ for (const rawLine of match[1].split(/\r?\n/)) {
140
+ const line = rawLine.trim();
141
+ if (!line || line.startsWith("#")) {
142
+ continue;
143
+ }
144
+
145
+ const separatorIndex = line.indexOf(":");
146
+ if (separatorIndex <= 0) {
147
+ continue;
148
+ }
149
+
150
+ const key = line.slice(0, separatorIndex).trim();
151
+ const value = line.slice(separatorIndex + 1).trim();
152
+ metadata[key] = normalizeScalar(value);
153
+ }
154
+
155
+ return Object.keys(metadata).length > 0 ? metadata : null;
156
+ }
157
+
158
+ function recoverTaskFrontmatter(body) {
159
+ const detailsStart = body.search(/<details><summary>[^<]*frontmatter[^<]*<\/summary>/i);
160
+ if (detailsStart < 0) {
161
+ return null;
162
+ }
163
+
164
+ const detailsBody = body.slice(detailsStart);
165
+ const yamlMatch = detailsBody.match(/```yaml\s*([\s\S]*?)\s*```/i);
166
+ if (!yamlMatch) {
167
+ return null;
168
+ }
169
+
170
+ return parseFrontmatterBlock(yamlMatch[1].trim());
171
+ }
172
+
173
+ function collectCandidates(comments) {
174
+ const byTaskId = new Map();
175
+
176
+ for (const comment of comments) {
177
+ const match = firstLine(comment.body).match(markerPattern);
178
+ if (!match) {
179
+ continue;
180
+ }
181
+
182
+ const [, taskId, fileStem] = match;
183
+ const existing = byTaskId.get(taskId) || {
184
+ task_id: taskId,
185
+ first_seen_at: comment.created_at || "",
186
+ has_task_comment: false,
187
+ task_comment_body: ""
188
+ };
189
+
190
+ if (!existing.first_seen_at || (comment.created_at && comment.created_at < existing.first_seen_at)) {
191
+ existing.first_seen_at = comment.created_at;
192
+ }
193
+
194
+ if (fileStem === "task") {
195
+ existing.has_task_comment = true;
196
+ existing.task_comment_body = comment.body || "";
197
+ }
198
+
199
+ byTaskId.set(taskId, existing);
200
+ }
201
+
202
+ return [...byTaskId.values()].sort((left, right) => {
203
+ const createdComparison = String(left.first_seen_at || "").localeCompare(String(right.first_seen_at || ""));
204
+ if (createdComparison !== 0) {
205
+ return createdComparison;
206
+ }
207
+ return left.task_id.localeCompare(right.task_id);
208
+ });
209
+ }
210
+
211
+ function buildResult(comments) {
212
+ const candidates = collectCandidates(comments);
213
+ if (candidates.length === 0) {
214
+ return { found: false };
215
+ }
216
+
217
+ const selectedCandidate = candidates[0];
218
+ const frontmatter = selectedCandidate.has_task_comment
219
+ ? recoverTaskFrontmatter(selectedCandidate.task_comment_body)
220
+ : null;
221
+
222
+ const result = {
223
+ found: true,
224
+ task_id: selectedCandidate.task_id
225
+ };
226
+
227
+ if (frontmatter) {
228
+ result.frontmatter = frontmatter;
229
+ }
230
+
231
+ return result;
232
+ }
233
+
234
+ function main() {
235
+ let args;
236
+ try {
237
+ args = parseArgs(process.argv.slice(2));
238
+ } catch (error) {
239
+ console.error(error.message);
240
+ console.error(usage());
241
+ process.exit(1);
242
+ }
243
+
244
+ if (args.help) {
245
+ console.log(usage());
246
+ return;
247
+ }
248
+
249
+ if (!args.issue) {
250
+ console.error("Missing required argument: --issue");
251
+ console.error(usage());
252
+ process.exit(1);
253
+ }
254
+
255
+ if (args.format !== "json") {
256
+ console.error(`Unsupported format: ${args.format}`);
257
+ process.exit(1);
258
+ }
259
+
260
+ try {
261
+ const repo = resolveRepo(args.repo);
262
+ const comments = fetchComments(repo, args.issue);
263
+ console.log(JSON.stringify(buildResult(comments), null, 2));
264
+ } catch (error) {
265
+ console.error(`Cannot scan issue comments: ${error.stderr || error.message}`);
266
+ process.exit(2);
267
+ }
268
+ }
269
+
270
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
271
+ main();
272
+ }
@@ -0,0 +1,5 @@
1
+ import { pathToFileURL } from "node:url";
2
+
3
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
4
+ console.log(JSON.stringify({ found: false }, null, 2));
5
+ }
@@ -9,6 +9,26 @@ const DEFAULT_RETRY_DELAYS_MS = [3000, 10000];
9
9
  let activeShared = null;
10
10
  let repoRoot = "";
11
11
 
12
+ export function getDefaults() {
13
+ return {
14
+ statusLabels: {
15
+ pendingDesignWork: "status: pending-design-work",
16
+ inProgress: "status: in-progress",
17
+ blocked: "status: blocked",
18
+ completed: "status: completed",
19
+ waitingForTriage: "status: waiting-for-triage"
20
+ },
21
+ markers: {
22
+ task: "<!-- sync-issue:{task-id}:task -->",
23
+ artifact: "<!-- sync-issue:{task-id}:{artifact-stem} -->",
24
+ artifactChunk: "<!-- sync-issue:{task-id}:{artifact-stem}:{part}/{total} -->",
25
+ summary: "<!-- sync-issue:{task-id}:summary -->",
26
+ cancel: "<!-- sync-issue:{task-id}:cancel -->",
27
+ prSummary: "<!-- sync-pr:{task-id}:summary -->"
28
+ }
29
+ };
30
+ }
31
+
12
32
  function getShared() {
13
33
  if (!activeShared) {
14
34
  throw new Error("platform-sync adapter shared utilities are unavailable");
@@ -124,12 +144,16 @@ function buildSyncContext({ taskDir, config, artifactFile }) {
124
144
  return { earlyReturn: blockedResult(CHECK_TYPE, upstreamRepo.message, "network_error") };
125
145
  }
126
146
  const permissions = detectPermissions(upstreamRepo.value, taskDir);
147
+ const expectedValues = resolveExpectedValues(config);
148
+ if (!expectedValues.ok) {
149
+ return { earlyReturn: failResult(CHECK_TYPE, expectedValues.message, "check_failed") };
150
+ }
127
151
 
128
- const marker = config.expected_comment_marker
129
- ? interpolate(config.expected_comment_marker, taskDir, artifactFile)
152
+ const marker = expectedValues.commentMarker
153
+ ? interpolate(expectedValues.commentMarker, taskDir, artifactFile)
130
154
  : null;
131
- const prMarker = config.expected_pr_comment_marker
132
- ? interpolate(config.expected_pr_comment_marker, taskDir, artifactFile)
155
+ const prMarker = expectedValues.prCommentMarker
156
+ ? interpolate(expectedValues.prCommentMarker, taskDir, artifactFile)
133
157
  : null;
134
158
  const artifactPath = artifactFile ? path.join(taskDir, artifactFile) : null;
135
159
 
@@ -144,11 +168,65 @@ function buildSyncContext({ taskDir, config, artifactFile }) {
144
168
  upstreamRepo: upstreamRepo.value,
145
169
  hasTriage: permissions.hasTriage,
146
170
  hasPush: permissions.hasPush,
171
+ expectedStatusLabel: expectedValues.statusLabel,
147
172
  marker,
148
173
  prMarker
149
174
  };
150
175
  }
151
176
 
177
+ function resolveExpectedValues(config) {
178
+ const defaults = getDefaults();
179
+ const statusLabel = resolveDefaultValue({
180
+ collection: defaults.statusLabels,
181
+ key: config.expected_status_label_key,
182
+ value: config.expected_status_label,
183
+ configKey: "expected_status_label_key"
184
+ });
185
+ if (!statusLabel.ok) {
186
+ return statusLabel;
187
+ }
188
+
189
+ const commentMarker = resolveDefaultValue({
190
+ collection: defaults.markers,
191
+ key: config.expected_comment_marker_key,
192
+ value: config.expected_comment_marker,
193
+ configKey: "expected_comment_marker_key"
194
+ });
195
+ if (!commentMarker.ok) {
196
+ return commentMarker;
197
+ }
198
+
199
+ const prCommentMarker = resolveDefaultValue({
200
+ collection: defaults.markers,
201
+ key: config.expected_pr_comment_marker_key,
202
+ value: config.expected_pr_comment_marker,
203
+ configKey: "expected_pr_comment_marker_key"
204
+ });
205
+ if (!prCommentMarker.ok) {
206
+ return prCommentMarker;
207
+ }
208
+
209
+ return {
210
+ ok: true,
211
+ statusLabel: statusLabel.value,
212
+ commentMarker: commentMarker.value,
213
+ prCommentMarker: prCommentMarker.value
214
+ };
215
+ }
216
+
217
+ function resolveDefaultValue({ collection, key, value, configKey }) {
218
+ if (!key) {
219
+ return { ok: true, value: value || null };
220
+ }
221
+
222
+ const resolvedValue = collection[key];
223
+ if (!resolvedValue) {
224
+ return { ok: false, message: `Unknown ${configKey}: ${key}` };
225
+ }
226
+
227
+ return { ok: true, value: resolvedValue };
228
+ }
229
+
152
230
  function fetchRemoteData(context) {
153
231
  let issueResult = withRetry(() => ghJson([
154
232
  "issue",
@@ -209,7 +287,7 @@ function fetchRemoteData(context) {
209
287
  }
210
288
 
211
289
  let prComments = null;
212
- if (context.config.expected_pr_comment_marker) {
290
+ if (context.prMarker) {
213
291
  if (!context.prNumber) {
214
292
  return {
215
293
  earlyReturn: failResult(CHECK_TYPE, "Expected a valid pr_number for PR comment verification", "check_failed")
@@ -323,7 +401,9 @@ function mapTaskTypeToLabel(taskType) {
323
401
  function shouldFetchComments(config) {
324
402
  return Boolean(
325
403
  config.expected_comment_marker
404
+ || config.expected_comment_marker_key
326
405
  || config.expected_pr_comment_marker
406
+ || config.expected_pr_comment_marker_key
327
407
  || config.verify_pr_comment_last_commit_matches_head
328
408
  || config.verify_comment_content
329
409
  || config.verify_task_comment_content
@@ -339,7 +419,7 @@ function flattenComments(value) {
339
419
  }
340
420
 
341
421
  function checkStatusLabel(context, remoteData) {
342
- if (!context.config.expected_status_label || !context.hasTriage) {
422
+ if (!context.expectedStatusLabel || !context.hasTriage) {
343
423
  return null;
344
424
  }
345
425
 
@@ -348,12 +428,12 @@ function checkStatusLabel(context, remoteData) {
348
428
  }
349
429
 
350
430
  const labels = extractLabelNames(remoteData.issue.labels);
351
- if (labels.includes(context.config.expected_status_label)) {
431
+ if (labels.includes(context.expectedStatusLabel)) {
352
432
  return null;
353
433
  }
354
434
 
355
435
  return failResult(CHECK_TYPE,
356
- `Expected label '${context.config.expected_status_label}' not found on Issue #${context.issueNumber}`,
436
+ `Expected label '${context.expectedStatusLabel}' not found on Issue #${context.issueNumber}`,
357
437
  "check_failed"
358
438
  );
359
439
  }
@@ -418,7 +498,7 @@ function checkPrCommentLastCommit(context, remoteData) {
418
498
  );
419
499
  }
420
500
 
421
- const headResult = withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
501
+ const headResult = resolvePrHeadSha(context);
422
502
  if (!headResult.ok) {
423
503
  return headResult.type === "check_failed"
424
504
  ? failResult(CHECK_TYPE, headResult.message, headResult.type)
@@ -1057,6 +1137,51 @@ function gitText(args, cwd) {
1057
1137
  return { ok: true, value: String(result.stdout || "").trim() };
1058
1138
  }
1059
1139
 
1140
+ function resolvePrHeadSha(context) {
1141
+ const fallback = () => withRetry(() => gitText(["rev-parse", "HEAD"], context.taskDir));
1142
+ const branch = String(context.task?.metadata?.branch || "").trim();
1143
+ if (!branch) {
1144
+ return fallback();
1145
+ }
1146
+
1147
+ const worktreeList = withRetry(() => gitText(["worktree", "list", "--porcelain"], context.taskDir));
1148
+ if (!worktreeList.ok) {
1149
+ return fallback();
1150
+ }
1151
+
1152
+ const matchedWorktree = findWorktreeForBranch(worktreeList.value, branch);
1153
+ if (!matchedWorktree) {
1154
+ return fallback();
1155
+ }
1156
+
1157
+ const headInWorktree = withRetry(() => gitText(["rev-parse", "HEAD"], matchedWorktree));
1158
+ if (!headInWorktree.ok) {
1159
+ return fallback();
1160
+ }
1161
+
1162
+ return headInWorktree;
1163
+ }
1164
+
1165
+ function findWorktreeForBranch(porcelainOutput, branch) {
1166
+ let currentWorktree = "";
1167
+ for (const rawLine of String(porcelainOutput || "").split("\n")) {
1168
+ const line = rawLine.trimEnd();
1169
+ if (line.startsWith("worktree ")) {
1170
+ currentWorktree = line.slice("worktree ".length).trim();
1171
+ continue;
1172
+ }
1173
+
1174
+ if (line.startsWith("branch refs/heads/")) {
1175
+ const usedBranch = line.slice("branch refs/heads/".length).trim();
1176
+ if (usedBranch === branch && currentWorktree) {
1177
+ return currentWorktree;
1178
+ }
1179
+ }
1180
+ }
1181
+
1182
+ return null;
1183
+ }
1184
+
1060
1185
  function withRetry(operation) {
1061
1186
  const delays = getRetryDelays();
1062
1187
  let lastFailure = null;
@@ -1,3 +1,10 @@
1
+ export function getDefaults() {
2
+ return {
3
+ statusLabels: {},
4
+ markers: {}
5
+ };
6
+ }
7
+
1
8
  export function check(_context, shared) {
2
9
  return shared.passResult(
3
10
  "platform-sync",
@@ -41,7 +41,7 @@ Read `task.md` carefully to understand:
41
41
  - currently known affected files and constraints
42
42
 
43
43
  If `task.md` contains these source fields, also read the corresponding source information:
44
- - `issue_number` - GitHub Issue
44
+ - `issue_number` - Issue
45
45
  - `codescan_alert_number` - Code Scanning alert
46
46
  - `security_alert_number` - Dependabot alert
47
47
 
@@ -70,7 +70,7 @@ Create `.agents/workspace/active/{task-id}/{analysis-artifact}`.
70
70
 
71
71
  ## Requirement Source
72
72
 
73
- **Source type**: {User description / GitHub Issue / Code Scanning / Dependabot / Other}
73
+ **Source type**: {User description / Issue / Code Scanning / Dependabot / Other}
74
74
  **Source summary**:
75
75
  > {Task source or key context}
76
76
 
@@ -121,7 +121,7 @@ Update `.agents/workspace/active/{task-id}/task.md`:
121
121
  If task.md contains a valid `issue_number`, perform these sync actions (skip and continue on any failure):
122
122
  - Read `.agents/rules/issue-sync.md` before syncing, and complete upstream repository detection plus permission detection
123
123
  - Set `status: pending-design-work` by following issue-sync.md
124
- - Create or update the `<!-- sync-issue:{task-id}:task -->` comment (follow the task.md comment sync rule in issue-sync.md)
124
+ - Create or update the task comment marker defined in `.agents/rules/issue-sync.md` (follow the task.md comment sync rule in issue-sync.md)
125
125
  - Publish the `{analysis-artifact}` comment
126
126
 
127
127
  ### 7. Verification Gate
@@ -41,7 +41,7 @@ description: "分析任务并输出需求分析文档"
41
41
  - 当前已知的受影响文件和约束
42
42
 
43
43
  如 `task.md` 包含以下来源字段,补充读取对应来源信息:
44
- - `issue_number` - GitHub Issue
44
+ - `issue_number` - Issue
45
45
  - `codescan_alert_number` - Code Scanning 告警
46
46
  - `security_alert_number` - Dependabot 告警
47
47
 
@@ -70,7 +70,7 @@ description: "分析任务并输出需求分析文档"
70
70
 
71
71
  ## 需求来源
72
72
 
73
- **来源类型**:{用户描述 / GitHub Issue / Code Scanning / Dependabot / 其他}
73
+ **来源类型**:{用户描述 / Issue / Code Scanning / Dependabot / 其他}
74
74
  **来源摘要**:
75
75
  > {任务来源或关键上下文}
76
76
 
@@ -121,7 +121,7 @@ date "+%Y-%m-%d %H:%M:%S%:z"
121
121
  如果 task.md 中存在有效的 `issue_number`,执行以下同步操作(任一失败则跳过并继续):
122
122
  - 执行前先读取 `.agents/rules/issue-sync.md`,完成 upstream 仓库检测和权限检测
123
123
  - 按 issue-sync.md 设置 `status: pending-design-work`
124
- - 创建或更新 `<!-- sync-issue:{task-id}:task -->` 评论(按 issue-sync.md 的 task.md 评论同步规则)
124
+ - 创建或更新 `.agents/rules/issue-sync.md` 中定义的 task 评论标记(按 issue-sync.md 的 task.md 评论同步规则)
125
125
  - 发布 `{analysis-artifact}` 评论
126
126
 
127
127
  ### 7. 完成校验
@@ -37,7 +37,9 @@
37
37
  "verify_comment_content": true,
38
38
  "verify_task_comment_content": true,
39
39
  "verify_issue_type": true,
40
- "verify_milestone": true
40
+ "verify_milestone": true,
41
+ "expected_status_label_key": "pendingDesignWork",
42
+ "expected_comment_marker_key": "artifact"
41
43
  }
42
44
  }
43
45
  }
@@ -22,7 +22,8 @@
22
22
  },
23
23
  "platform-sync": {
24
24
  "when": "issue_number_exists",
25
- "expected_status_label": "status: blocked"
25
+ "expected_status_label": "status: blocked",
26
+ "expected_status_label_key": "blocked"
26
27
  }
27
28
  }
28
29
  }
@@ -79,8 +79,8 @@ If a valid `issue_number` exists:
79
79
  - Remove all `in:` labels by following issue-sync.md
80
80
  - Remove the milestone by following issue-sync.md
81
81
  - Remove all assignees (skip directly when permission is insufficient; no fallback)
82
- - Publish a cancellation comment using the marker `<!-- sync-issue:{task-id}:cancel -->`
83
- - Create or update the `<!-- sync-issue:{task-id}:task -->` comment using the task-comment sync rules from `.agents/rules/issue-sync.md`
82
+ - Publish a cancellation comment using the cancel marker defined in `.agents/rules/issue-sync.md`
83
+ - Create or update the task comment marker defined in `.agents/rules/issue-sync.md` using the task-comment sync rules from `.agents/rules/issue-sync.md`
84
84
  - Close the Issue by following the "Close an Issue" command in `.agents/rules/issue-pr-commands.md`, using the fixed reason `not planned`
85
85
 
86
86
  The cancellation comment must include at least:
@@ -79,8 +79,8 @@ ls .agents/workspace/completed/{task-id}/task.md
79
79
  - 按 issue-sync.md 移除所有 `in:` labels
80
80
  - 按 issue-sync.md 移除 milestone
81
81
  - 移除全部 assignees(无权限时直接跳过,不做替代)
82
- - 发布取消评论,隐藏标记使用 `<!-- sync-issue:{task-id}:cancel -->`
83
- - 使用 `.agents/rules/issue-sync.md` 的 task.md 评论同步规则创建或更新 `<!-- sync-issue:{task-id}:task -->` 评论
82
+ - 发布取消评论,隐藏标记使用 `.agents/rules/issue-sync.md` 中定义的 cancel 评论标记
83
+ - 使用 `.agents/rules/issue-sync.md` 的 task.md 评论同步规则创建或更新 `.agents/rules/issue-sync.md` 中定义的 task 评论标记
84
84
  - 关闭 Issue:按 `.agents/rules/issue-pr-commands.md` 中的“关闭 Issue”命令执行,关闭原因固定为 `not planned`
85
85
 
86
86
  取消评论至少包含:
@@ -24,7 +24,8 @@
24
24
  "platform-sync": {
25
25
  "when": "issue_number_exists",
26
26
  "expected_comment_marker": "<!-- sync-issue:{task-id}:cancel -->",
27
- "verify_task_comment_content": true
27
+ "verify_task_comment_content": true,
28
+ "expected_comment_marker_key": "cancel"
28
29
  }
29
30
  }
30
31
  }