@fitlab-ai/agent-infra 0.8.3 → 0.8.5

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 (122) hide show
  1. package/README.zh-CN.md +10 -0
  2. package/bin/cli.ts +1 -1
  3. package/dist/bin/cli.js +1 -1
  4. package/dist/lib/cp.js +56 -15
  5. package/dist/lib/defaults.json +9 -0
  6. package/dist/lib/init.js +5 -0
  7. package/dist/lib/sandbox/capture.js +5 -3
  8. package/dist/lib/sandbox/clipboard/bridge.js +12 -9
  9. package/dist/lib/sandbox/clipboard/inbox.js +82 -0
  10. package/dist/lib/sandbox/commands/create.js +68 -26
  11. package/dist/lib/sandbox/engine.js +33 -3
  12. package/dist/lib/sandbox/engines/colima.js +2 -2
  13. package/dist/lib/sandbox/runtimes/base.dockerfile +7 -0
  14. package/dist/lib/sandbox/shell.js +9 -1
  15. package/dist/lib/sandbox/tools.js +2 -2
  16. package/dist/lib/server/daemon.js +9 -0
  17. package/dist/lib/server/process-control.js +66 -73
  18. package/dist/lib/server/process-state.js +168 -0
  19. package/dist/lib/update.js +2 -0
  20. package/lib/cp.ts +56 -15
  21. package/lib/defaults.json +9 -0
  22. package/lib/init.ts +9 -0
  23. package/lib/sandbox/capture.ts +5 -3
  24. package/lib/sandbox/clipboard/bridge.ts +14 -12
  25. package/lib/sandbox/clipboard/inbox.ts +83 -0
  26. package/lib/sandbox/commands/create.ts +80 -27
  27. package/lib/sandbox/engine.ts +37 -3
  28. package/lib/sandbox/engines/colima.ts +2 -2
  29. package/lib/sandbox/runtimes/base.dockerfile +7 -0
  30. package/lib/sandbox/shell.ts +11 -2
  31. package/lib/sandbox/tools.ts +5 -5
  32. package/lib/server/daemon.ts +9 -0
  33. package/lib/server/process-control.ts +70 -71
  34. package/lib/server/process-state.ts +183 -0
  35. package/lib/update.ts +6 -0
  36. package/package.json +2 -1
  37. package/runtime/platform-adapters/platform-sync.github.js +1501 -0
  38. package/templates/.agents/README.en.md +5 -0
  39. package/templates/.agents/README.zh-CN.md +5 -0
  40. package/templates/.agents/rules/create-issue.github.en.md +1 -1
  41. package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
  42. package/templates/.agents/rules/review-handshake.en.md +5 -4
  43. package/templates/.agents/rules/review-handshake.zh-CN.md +5 -4
  44. package/templates/.agents/rules/testing-discipline.en.md +3 -41
  45. package/templates/.agents/rules/testing-discipline.zh-CN.md +3 -41
  46. package/templates/.agents/scripts/lib/agent-infra-package.js +149 -0
  47. package/templates/.agents/scripts/lib/post-review-commit.js +5 -0
  48. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +25 -1459
  49. package/templates/.agents/scripts/review-diff-fingerprint.js +95 -24
  50. package/templates/.agents/scripts/validate-artifact.js +144 -38
  51. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  52. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  53. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +1 -1
  54. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  55. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  56. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  57. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  58. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +9 -9
  59. package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
  60. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  61. package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
  62. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  63. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  64. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  65. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +2 -2
  66. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +2 -2
  67. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +2 -2
  68. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  69. package/templates/.agents/skills/commit/SKILL.en.md +6 -4
  70. package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -4
  71. package/templates/.agents/skills/commit/reference/task-status-update.en.md +35 -8
  72. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +35 -8
  73. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +27 -3
  74. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +27 -3
  75. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  76. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  77. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  78. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  79. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  80. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  81. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  82. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  83. package/templates/.agents/skills/create-task/SKILL.en.md +2 -2
  84. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  85. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -2
  86. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +3 -3
  87. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
  88. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  89. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -2
  90. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
  91. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +1 -1
  92. package/templates/.agents/skills/init-milestones/SKILL.en.md +5 -4
  93. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +6 -5
  94. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +170 -45
  95. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  96. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  97. package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
  98. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
  99. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  100. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  101. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  102. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  103. package/templates/.agents/skills/review-code/SKILL.en.md +8 -4
  104. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +6 -3
  105. package/templates/.agents/skills/review-code/config/verify.en.json +3 -1
  106. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +3 -1
  107. package/templates/.agents/skills/review-code/reference/output-templates.en.md +5 -5
  108. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +5 -5
  109. package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -2
  110. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -2
  111. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  112. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  113. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  114. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  115. package/templates/.agents/skills/test/SKILL.zh-CN.md +26 -8
  116. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -1
  117. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +6 -2
  118. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +203 -142
  119. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  120. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  121. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +8 -1
  122. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +8 -1
@@ -13,7 +13,8 @@ const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
13
13
  }).trim();
14
14
 
15
15
  function usage() {
16
- console.error("Usage: node .agents/scripts/review-diff-fingerprint.js <worktree|staged> <baseline>");
16
+ console.error("Usage: node .agents/scripts/review-diff-fingerprint.js <worktree|staged> <baseline> [--format json]");
17
+ console.error(" or: node .agents/scripts/review-diff-fingerprint.js compare <expected-tree> <actual-tree> [--format json]");
17
18
  process.exit(2);
18
19
  }
19
20
 
@@ -42,6 +43,15 @@ function splitNull(output) {
42
43
  return output.split("\0").filter((value) => value !== "");
43
44
  }
44
45
 
46
+ function pathExists(filePath) {
47
+ try {
48
+ fs.lstatSync(filePath);
49
+ return true;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
45
55
  function hashDiff(args, env = process.env) {
46
56
  const diff = execFileSync("git", args, {
47
57
  cwd: repoRoot,
@@ -51,49 +61,110 @@ function hashDiff(args, env = process.env) {
51
61
  return `sha256:${crypto.createHash("sha256").update(diff).digest("hex")}`;
52
62
  }
53
63
 
54
- function worktreeFingerprint(baseline, globs) {
64
+ function withTemporaryIndex(baseline, callback) {
55
65
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-infra-review-index-"));
56
66
  const tempIndex = path.join(tempDir, "index");
57
67
  const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
58
68
 
59
69
  try {
60
70
  execFileSync("git", ["read-tree", baseline], { cwd: repoRoot, env });
71
+ return callback(env);
72
+ } finally {
73
+ fs.rmSync(tempDir, { recursive: true, force: true });
74
+ }
75
+ }
61
76
 
62
- const tracked = splitNull(git(["diff", "--name-only", "-z", baseline, "--", ...globs]));
63
- const untracked = splitNull(git(["ls-files", "-o", "--exclude-standard", "-z", "--", ...globs]));
64
- const paths = [...new Set([...tracked, ...untracked])];
65
-
66
- for (const filePath of paths) {
67
- const absolutePath = path.join(repoRoot, filePath);
68
- if (fs.existsSync(absolutePath)) {
69
- execFileSync("git", ["update-index", "--add", "--", filePath], { cwd: repoRoot, env });
70
- } else {
71
- execFileSync("git", ["update-index", "--remove", "--", filePath], { cwd: repoRoot, env });
72
- }
77
+ function projectWorktree(baseline, globs, env) {
78
+ const tracked = splitNull(git(["diff", "--name-only", "-z", baseline, "--", ...globs]));
79
+ const untracked = splitNull(git(["ls-files", "-o", "--exclude-standard", "-z", "--", ...globs]));
80
+ const paths = [...new Set([...tracked, ...untracked])];
81
+
82
+ for (const filePath of paths) {
83
+ const absolutePath = path.join(repoRoot, filePath);
84
+ if (pathExists(absolutePath)) {
85
+ execFileSync("git", ["update-index", "--add", "--", filePath], { cwd: repoRoot, env });
86
+ } else {
87
+ execFileSync("git", ["update-index", "--remove", "--", filePath], { cwd: repoRoot, env });
73
88
  }
89
+ }
90
+ }
74
91
 
75
- return hashDiff(["diff", "--cached", "--binary", baseline, "--", ...globs], env);
76
- } finally {
77
- fs.rmSync(tempDir, { recursive: true, force: true });
92
+ function projectStaged(baseline, globs, env) {
93
+ const paths = splitNull(git(["diff", "--cached", "--name-only", "-z", baseline, "--", ...globs]));
94
+ for (const filePath of paths) {
95
+ const entry = execFileSync(
96
+ "git",
97
+ ["--literal-pathspecs", "ls-files", "--stage", "-z", "--", filePath],
98
+ { cwd: repoRoot, encoding: "buffer" }
99
+ );
100
+ const indexInfo = entry.length > 0
101
+ ? entry
102
+ : Buffer.from(`0 ${"0".repeat(40)}\t${filePath}\0`);
103
+ execFileSync("git", ["update-index", "-z", "--index-info"], {
104
+ cwd: repoRoot,
105
+ env,
106
+ input: indexInfo
107
+ });
78
108
  }
79
109
  }
80
110
 
81
- function stagedFingerprint(baseline, globs) {
82
- return hashDiff(["diff", "--cached", "--binary", baseline, "--", ...globs]);
111
+ function snapshot(mode, baseline, globs) {
112
+ return withTemporaryIndex(baseline, (env) => {
113
+ if (mode === "worktree") {
114
+ projectWorktree(baseline, globs, env);
115
+ } else {
116
+ projectStaged(baseline, globs, env);
117
+ }
118
+ return {
119
+ baseline,
120
+ fingerprint: hashDiff(["diff", "--cached", "--binary", baseline, "--", ...globs], env),
121
+ tree: git(["write-tree"], { env }).trim()
122
+ };
123
+ });
124
+ }
125
+
126
+ function compareTrees(expected, actual) {
127
+ git(["rev-parse", "--verify", `${expected}^{tree}`]);
128
+ git(["rev-parse", "--verify", `${actual}^{tree}`]);
129
+ const fields = splitNull(git(["diff", "--name-status", "-z", "--no-renames", expected, actual]));
130
+ const result = { equal: fields.length === 0, added: [], missing: [], different: [] };
131
+ for (let index = 0; index < fields.length; index += 2) {
132
+ const status = fields[index];
133
+ const filePath = fields[index + 1];
134
+ if (status === "A") {
135
+ result.added.push(filePath);
136
+ } else if (status === "D") {
137
+ result.missing.push(filePath);
138
+ } else {
139
+ result.different.push(filePath);
140
+ }
141
+ }
142
+ return result;
83
143
  }
84
144
 
85
145
  function main(argv) {
86
- const [mode, baseline] = argv;
146
+ const [mode, first, second] = argv;
147
+ const json = argv.includes("--format") && argv[argv.indexOf("--format") + 1] === "json";
148
+
149
+ if (mode === "compare") {
150
+ if (!first || !second) {
151
+ usage();
152
+ }
153
+ const result = compareTrees(first, second);
154
+ process.stdout.write(json ? `${JSON.stringify(result)}\n` : `${result.equal ? "equal" : "different"}\n`);
155
+ process.exitCode = result.equal ? 0 : 1;
156
+ return;
157
+ }
158
+
159
+ const baseline = first;
87
160
  if (!["worktree", "staged"].includes(mode) || !baseline) {
88
161
  usage();
89
162
  }
90
163
 
91
- git(["rev-parse", "--verify", `${baseline}^{commit}`]);
164
+ const resolvedBaseline = git(["rev-parse", "--verify", `${baseline}^{commit}`]).trim();
92
165
  const globs = resolvePostReviewGlobs({}, loadReviewConfig());
93
- const fingerprint = mode === "worktree"
94
- ? worktreeFingerprint(baseline, globs)
95
- : stagedFingerprint(baseline, globs);
96
- process.stdout.write(`${fingerprint}\n`);
166
+ const result = snapshot(mode, resolvedBaseline, globs);
167
+ process.stdout.write(json ? `${JSON.stringify(result)}\n` : `${result.fingerprint}\n`);
97
168
  }
98
169
 
99
170
  main(process.argv.slice(2));
@@ -6,7 +6,10 @@ import { fileURLToPath } from "node:url";
6
6
 
7
7
  import {
8
8
  extractReviewBaseline,
9
+ extractReviewDiffFingerprint,
10
+ extractReviewedSnapshotTree,
9
11
  findAuthoritativeReviewCodeArtifact,
12
+ parseReviewVerdict,
10
13
  resolvePostReviewGlobs
11
14
  } from "./lib/post-review-commit.js";
12
15
 
@@ -223,6 +226,8 @@ function runCheck(type, context) {
223
226
  return checkCompletionChecklist(context);
224
227
  case "review-ledger":
225
228
  return checkReviewLedger(context);
229
+ case "review-fact":
230
+ return checkReviewFact(context);
226
231
  case "post-review-commit":
227
232
  return checkPostReviewCommit(context);
228
233
  default: {
@@ -775,12 +780,10 @@ function checkPostReviewCommit({ taskDir, config }) {
775
780
 
776
781
  const task = loadTask(taskDir);
777
782
  const content = fs.readFileSync(reviewArtifact.path, "utf8");
778
- const reviewBaseline = extractReviewBaseline(content);
779
783
  const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
780
784
  const baselineSource = resolvePostReviewBaseline({
781
785
  gitRoot,
782
786
  lastReviewedCommit,
783
- reviewBaseline,
784
787
  reviewArtifact: reviewArtifact.fileName
785
788
  });
786
789
  if (!baselineSource.ok) {
@@ -814,39 +817,123 @@ function checkPostReviewCommit({ taskDir, config }) {
814
817
 
815
818
  return failResult(
816
819
  "post-review-commit",
817
- `${commits.length} commit(s) to code/rule paths after review baseline ${sha.slice(0, 8)}; re-run review-code or record a human-decided exemption`
820
+ `${commits.length} commit(s) to code/rule paths after reviewed commit ${sha.slice(0, 8)}; re-run review-code or record a human-decided exemption`
818
821
  );
819
822
  }
820
823
 
821
- function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewBaseline, reviewArtifact }) {
822
- if (lastReviewedCommit) {
823
- if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
824
- return { ok: true, sha: lastReviewedCommit };
825
- }
824
+ function checkReviewFact({ taskDir, artifactFile }) {
825
+ const resolvedArtifact = resolveArtifactPath(
826
+ taskDir,
827
+ "review-code.md|review-code-r{N}.md",
828
+ artifactFile
829
+ );
830
+ if (!resolvedArtifact.ok) {
831
+ return failResult("review-fact", resolvedArtifact.message);
832
+ }
833
+
834
+ const task = loadTask(taskDir);
835
+ if (!task.ok) {
836
+ return failResult("review-fact", task.message);
837
+ }
838
+
839
+ const content = fs.readFileSync(resolvedArtifact.path, "utf8");
840
+ const verdict = parseReviewVerdict(content);
841
+ const reviewBaseline = extractReviewBaseline(content);
842
+ const reviewedFingerprint = extractReviewDiffFingerprint(content);
843
+ const reviewedTree = extractReviewedSnapshotTree(content);
844
+
845
+ if (!["通过", "需要修改", "拒绝", "Approved", "Changes Requested", "Rejected"].includes(verdict)) {
846
+ return failResult("review-fact", `Unsupported review verdict '${verdict}'`);
847
+ }
848
+
849
+ let gitRoot;
850
+ let head;
851
+ let baseline;
852
+ try {
853
+ gitRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
854
+ head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
855
+ baseline = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewBaseline}^{commit}`], { encoding: "utf8" }).trim();
856
+ } catch {
857
+ return blockedResult(
858
+ "review-fact",
859
+ `Unable to resolve review baseline '${reviewBaseline}' in the task repository; re-run review-code`
860
+ );
861
+ }
862
+
863
+ if (baseline !== head) {
864
+ return failResult(
865
+ "review-fact",
866
+ `Review baseline ${baseline.slice(0, 8)} does not match current HEAD ${head.slice(0, 8)}; re-run review-code`
867
+ );
868
+ }
869
+
870
+ let actualSnapshot;
871
+ try {
872
+ actualSnapshot = JSON.parse(execFileSync(
873
+ process.execPath,
874
+ [path.join(repoRoot, ".agents", "scripts", "review-diff-fingerprint.js"), "worktree", baseline, "--format", "json"],
875
+ { cwd: gitRoot, encoding: "utf8" }
876
+ ));
877
+ } catch {
878
+ return blockedResult(
879
+ "review-fact",
880
+ `Unable to recompute reviewed diff fingerprint from baseline ${baseline.slice(0, 8)}; re-run review-code`
881
+ );
882
+ }
883
+
884
+ if (actualSnapshot.fingerprint !== reviewedFingerprint) {
885
+ return failResult(
886
+ "review-fact",
887
+ `Reviewed diff fingerprint does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`
888
+ );
826
889
  }
827
890
 
828
- if (!reviewBaseline) {
829
- return {
830
- ok: false,
831
- result: passResult(
832
- "post-review-commit",
833
- `${reviewArtifact} predates baseline-commit anchoring; skipped (legacy artifact)`,
834
- [`${reviewArtifact} has no 审查基线提交 / Review Baseline Commit field`]
835
- )
836
- };
891
+ if (!reviewedTree || actualSnapshot.tree !== reviewedTree) {
892
+ return failResult(
893
+ "review-fact",
894
+ `Reviewed snapshot tree does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`
895
+ );
837
896
  }
838
897
 
839
- if (!SHA_PATTERN.test(reviewBaseline)) {
840
- return {
841
- ok: false,
842
- result: blockedResult(
843
- "post-review-commit",
844
- `${reviewArtifact} has an empty or malformed 审查基线提交 SHA ('${reviewBaseline}'); manual remediation required`
845
- )
846
- };
898
+ if (["通过", "Approved"].includes(verdict)) {
899
+ const lastReviewedCommit = String(task.metadata.last_reviewed_commit || "").trim();
900
+ const cleanSnapshot = actualSnapshot.tree === execFileSync(
901
+ "git",
902
+ ["-C", gitRoot, "rev-parse", `${baseline}^{tree}`],
903
+ { encoding: "utf8" }
904
+ ).trim();
905
+ if (cleanSnapshot && lastReviewedCommit !== baseline) {
906
+ return failResult(
907
+ "review-fact",
908
+ `Approved clean review must set task last_reviewed_commit to baseline ${baseline.slice(0, 8)}`
909
+ );
910
+ }
911
+ if (!cleanSnapshot && lastReviewedCommit) {
912
+ return failResult(
913
+ "review-fact",
914
+ "Approved review with uncommitted changes must remain unanchored until commit"
915
+ );
916
+ }
847
917
  }
848
918
 
849
- return { ok: true, sha: reviewBaseline };
919
+ return passResult(
920
+ "review-fact",
921
+ `Review fact valid for ${path.basename(resolvedArtifact.path)} at ${baseline.slice(0, 8)}`
922
+ );
923
+ }
924
+
925
+ function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
926
+ if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
927
+ return { ok: true, sha: lastReviewedCommit };
928
+ }
929
+
930
+ return {
931
+ ok: false,
932
+ result: blockedResult(
933
+ "post-review-commit",
934
+ `${reviewArtifact}: reviewed snapshot was not anchored to a valid commit; re-run commit or review-code`
935
+ )
936
+ };
850
937
  }
851
938
 
852
939
  function gitCommitExists(gitRoot, sha) {
@@ -942,22 +1029,41 @@ function parseFrontmatter(content) {
942
1029
  function getSectionContent(content, names) {
943
1030
  const lines = content.split(/\r?\n/);
944
1031
 
1032
+ function visibleHeadings() {
1033
+ const headings = [];
1034
+ let fence = null;
1035
+ for (let index = 0; index < lines.length; index += 1) {
1036
+ const line = lines[index];
1037
+ if (fence) {
1038
+ const closer = line.match(/^ {0,3}(`+|~+)\s*$/);
1039
+ if (closer && closer[1][0] === fence.character && closer[1].length >= fence.length) {
1040
+ fence = null;
1041
+ }
1042
+ continue;
1043
+ }
1044
+ const opener = line.match(/^ {0,3}(`{3,}|~{3,})(?:[^`~].*)?$/);
1045
+ if (opener) {
1046
+ fence = { character: opener[1][0], length: opener[1].length };
1047
+ continue;
1048
+ }
1049
+ if (line.startsWith("## ")) {
1050
+ headings.push({ index, text: line.trim() });
1051
+ }
1052
+ }
1053
+ return headings;
1054
+ }
1055
+
1056
+ const headings = visibleHeadings();
1057
+
945
1058
  for (const name of names) {
946
1059
  const heading = `## ${name}`;
947
- const startIndex = lines.findIndex((line) => line.trim() === heading);
948
- if (startIndex === -1) {
1060
+ const position = headings.findIndex((item) => item.text === heading);
1061
+ if (position === -1) {
949
1062
  continue;
950
1063
  }
951
-
952
- const sectionLines = [];
953
- for (let index = startIndex + 1; index < lines.length; index += 1) {
954
- if (lines[index].startsWith("## ")) {
955
- break;
956
- }
957
- sectionLines.push(lines[index]);
958
- }
959
-
960
- return sectionLines.join("\n").trim();
1064
+ const startIndex = headings[position].index;
1065
+ const endIndex = headings[position + 1]?.index ?? lines.length;
1066
+ return lines.slice(startIndex + 1, endIndex).join("\n").trim();
961
1067
  }
962
1068
 
963
1069
  return "";
@@ -210,7 +210,7 @@ Create `.agents/workspace/active/{task-id}/{analysis-artifact}`.
210
210
  Get the current time:
211
211
 
212
212
  ```bash
213
- date "+%Y-%m-%d %H:%M:%S%:z"
213
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
214
214
  ```
215
215
 
216
216
  Update `.agents/workspace/active/{task-id}/task.md`:
@@ -209,7 +209,7 @@ tail .agents/workspace/active/{task-id}/task.md
209
209
  获取当前时间:
210
210
 
211
211
  ```bash
212
- date "+%Y-%m-%d %H:%M:%S%:z"
212
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
213
213
  ```
214
214
 
215
215
  更新 `.agents/workspace/active/{task-id}/task.md`:
@@ -275,7 +275,7 @@ node .agents/scripts/validate-artifact.js gate analyze-task .agents/workspace/ac
275
275
 
276
276
  下一步 - 审查需求分析:
277
277
  - Claude Code / OpenCode:/review-analysis {task-ref}
278
- - Gemini CLI:/agent-infra:review-analysis {task-ref}
278
+ - Gemini CLI:/{{project}}:review-analysis {task-ref}
279
279
  - Codex CLI:$review-analysis {task-ref}
280
280
  ```
281
281
 
@@ -210,7 +210,7 @@ rebuild_manifest() {
210
210
  entries_file="$tmpdir/manifest.tsv"
211
211
  month_keys_file="$tmpdir/manifest-months.tsv"
212
212
  year_keys_file="$tmpdir/manifest-years.tsv"
213
- generated_at=$(date "+%Y-%m-%d %H:%M:%S%:z")
213
+ generated_at=$(date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/')
214
214
 
215
215
  mkdir -p "$ARCHIVE_DIR"
216
216
  : > "$entries_file"
@@ -58,7 +58,7 @@ Before blocking, thoroughly analyze:
58
58
  Get the current time:
59
59
 
60
60
  ```bash
61
- date "+%Y-%m-%d %H:%M:%S%:z"
61
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
62
62
  ```
63
63
 
64
64
  Update `.agents/workspace/active/{task-id}/task.md`:
@@ -57,7 +57,7 @@ description: >
57
57
  获取当前时间:
58
58
 
59
59
  ```bash
60
- date "+%Y-%m-%d %H:%M:%S%:z"
60
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
61
61
  ```
62
62
 
63
63
  更新 `.agents/workspace/active/{task-id}/task.md`:
@@ -140,7 +140,7 @@ ai sandbox rm {branch}
140
140
 
141
141
  下一步 - 检查任务状态(解除阻塞后):
142
142
  - Claude Code / OpenCode:/check-task {task-ref}
143
- - Gemini CLI:/agent-infra:check-task {task-ref}
143
+ - Gemini CLI:/{{project}}:check-task {task-ref}
144
144
  - Codex CLI:$check-task {task-ref}
145
145
  ```
146
146
 
@@ -58,7 +58,7 @@ When syncing to the Issue, replace any existing `status:` labels with the inferr
58
58
  Get the current time:
59
59
 
60
60
  ```bash
61
- date "+%Y-%m-%d %H:%M:%S%:z"
61
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
62
62
  ```
63
63
 
64
64
  Update `task.md` in the task directory:
@@ -57,7 +57,7 @@ description: >
57
57
  获取当前时间:
58
58
 
59
59
  ```bash
60
- date "+%Y-%m-%d %H:%M:%S%:z"
60
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
61
61
  ```
62
62
 
63
63
  更新任务目录中的 `task.md`:
@@ -149,7 +149,7 @@ ai sandbox rm {branch}
149
149
 
150
150
  下一步 - 查看已转移任务:
151
151
  - Claude Code / OpenCode:/check-task {task-ref}
152
- - Gemini CLI:/agent-infra:check-task {task-ref}
152
+ - Gemini CLI:/{{project}}:check-task {task-ref}
153
153
  - Codex CLI:$check-task {task-ref}
154
154
  ```
155
155
 
@@ -78,15 +78,15 @@ ai task status {task-id}
78
78
 
79
79
  | 当前状态 | Claude Code / OpenCode | Gemini CLI | Codex CLI |
80
80
  |--------------------|------------------------------|------------------------------------------|------------------------------|
81
- | 分析完成 | `/review-analysis {task-ref}` | `/agent-infra:review-analysis {task-ref}` | `$review-analysis {task-ref}` |
82
- | 需求分析审查通过 | `/plan-task {task-ref}` | `/agent-infra:plan-task {task-ref}` | `$plan-task {task-ref}` |
83
- | 需求分析审查有问题 | `/analyze-task {task-ref}` | `/agent-infra:analyze-task {task-ref}` | `$analyze-task {task-ref}` |
84
- | 计划完成 | `/review-plan {task-ref}` | `/agent-infra:review-plan {task-ref}` | `$review-plan {task-ref}` |
85
- | 技术方案审查通过 | `/code-task {task-ref}` | `/agent-infra:code-task {task-ref}` | `$code-task {task-ref}` |
86
- | 技术方案审查有问题 | `/plan-task {task-ref}` | `/agent-infra:plan-task {task-ref}` | `$plan-task {task-ref}` |
87
- | 实现完成 | `/review-code {task-ref}` | `/agent-infra:review-code {task-ref}` | `$review-code {task-ref}` |
88
- | 代码审查通过 | `/commit` | `/agent-infra:commit` | `$commit` |
89
- | 代码审查有问题 | `/code-task {task-ref}` | `/agent-infra:code-task {task-ref}` | `$code-task {task-ref}` |
81
+ | 分析完成 | `/review-analysis {task-ref}` | `/{{project}}:review-analysis {task-ref}` | `$review-analysis {task-ref}` |
82
+ | 需求分析审查通过 | `/plan-task {task-ref}` | `/{{project}}:plan-task {task-ref}` | `$plan-task {task-ref}` |
83
+ | 需求分析审查有问题 | `/analyze-task {task-ref}` | `/{{project}}:analyze-task {task-ref}` | `$analyze-task {task-ref}` |
84
+ | 计划完成 | `/review-plan {task-ref}` | `/{{project}}:review-plan {task-ref}` | `$review-plan {task-ref}` |
85
+ | 技术方案审查通过 | `/code-task {task-ref}` | `/{{project}}:code-task {task-ref}` | `$code-task {task-ref}` |
86
+ | 技术方案审查有问题 | `/plan-task {task-ref}` | `/{{project}}:plan-task {task-ref}` | `$plan-task {task-ref}` |
87
+ | 实现完成 | `/review-code {task-ref}` | `/{{project}}:review-code {task-ref}` | `$review-code {task-ref}` |
88
+ | 代码审查通过 | `/commit` | `/{{project}}:commit` | `$commit` |
89
+ | 代码审查有问题 | `/code-task {task-ref}` | `/{{project}}:code-task {task-ref}` | `$code-task {task-ref}` |
90
90
  | 任务被阻塞 | 解除阻塞或提供所需信息 | — | 解除阻塞或提供所需信息 |
91
91
  | 任务已完成 | 无需操作 | — | 无需操作 |
92
92
 
@@ -88,7 +88,7 @@ If a related task exists (search for `codescan_alert_number: <alert-number>`):
88
88
  Get the current time:
89
89
 
90
90
  ```bash
91
- date "+%Y-%m-%d %H:%M:%S%:z"
91
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
92
92
  ```
93
93
 
94
94
  - Add the dismissal record to task.md
@@ -88,7 +88,7 @@ Code Scanning 告警 #{alert-number}
88
88
  获取当前时间:
89
89
 
90
90
  ```bash
91
- date "+%Y-%m-%d %H:%M:%S%:z"
91
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
92
92
  ```
93
93
 
94
94
  - 添加关闭记录到 task.md
@@ -128,7 +128,7 @@ ai sandbox rm {branch}
128
128
 
129
129
  下一步 - 完成并归档任务(如有关联任务):
130
130
  - Claude Code / OpenCode:/complete-task {task-ref}
131
- - Gemini CLI:/agent-infra:complete-task {task-ref}
131
+ - Gemini CLI:/{{project}}:complete-task {task-ref}
132
132
  - Codex CLI:$complete-task {task-ref}
133
133
  ```
134
134
 
@@ -96,7 +96,7 @@ If a related task exists (search for `security_alert_number: <alert-number>`):
96
96
  Get the current time:
97
97
 
98
98
  ```bash
99
- date "+%Y-%m-%d %H:%M:%S%:z"
99
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
100
100
  ```
101
101
 
102
102
  - Add the dismissal record to task.md
@@ -96,7 +96,7 @@ CVE:{cve-id}
96
96
  获取当前时间:
97
97
 
98
98
  ```bash
99
- date "+%Y-%m-%d %H:%M:%S%:z"
99
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
100
100
  ```
101
101
 
102
102
  - 添加关闭记录到 task.md
@@ -136,7 +136,7 @@ ai sandbox rm {branch}
136
136
 
137
137
  下一步 - 完成并归档任务(如有关联任务):
138
138
  - Claude Code / OpenCode:/complete-task {task-ref}
139
- - Gemini CLI:/agent-infra:complete-task {task-ref}
139
+ - Gemini CLI:/{{project}}:complete-task {task-ref}
140
140
  - Codex CLI:$complete-task {task-ref}
141
141
  ```
142
142
 
@@ -114,7 +114,7 @@ Create `.agents/workspace/active/{task-id}/{code-artifact}`.
114
114
  Get the current time:
115
115
 
116
116
  ```bash
117
- date "+%Y-%m-%d %H:%M:%S%:z"
117
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
118
118
  ```
119
119
 
120
120
  Set `current_step` to `code`, refresh task metadata, and append one Activity Log entry:
@@ -156,7 +156,7 @@ echo "$result"
156
156
  获取当前时间:
157
157
 
158
158
  ```bash
159
- date "+%Y-%m-%d %H:%M:%S%:z"
159
+ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
160
160
  ```
161
161
 
162
162
  更新 `.agents/workspace/active/{task-id}/task.md`:
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 分支名规则
6
6
 
7
- - 格式:`agent-infra-{type}-{slug}`
7
+ - 格式:`{{project}}-{type}-{slug}`
8
8
  - 项目前缀:读取 `.agents/.airc.json` 中的 `project`
9
9
  - `{type}`:读取 `task.md` frontmatter 中的 `type`
10
10
  - `{slug}`:根据任务标题提取 3-6 个英文关键词,转为 kebab-case
@@ -23,7 +23,7 @@
23
23
  - 当前分支不一致:按下方”创建与切换命令”章节切换到已记录分支
24
24
 
25
25
  场景 B:`task.md` 未记录任务分支
26
- - 判断当前分支是否符合项目分支命名规范(`agent-infra-{type}-{slug}`)且语义上属于当前任务
26
+ - 判断当前分支是否符合项目分支命名规范(`{{project}}-{type}-{slug}`)且语义上属于当前任务
27
27
  - 符合:将当前分支名回写到 `task.md`,继续
28
28
  - 不符合:生成新的任务分支名,按下方”创建与切换命令”章节创建并切换,回写到 `task.md`
29
29
 
@@ -74,11 +74,11 @@ Fix status:
74
74
  Next step - re-review or commit:
75
75
  - Re-review (always recommended):
76
76
  - Claude Code / OpenCode: /review-code {task-ref}
77
- - Gemini CLI: /agent-infra:review-code {task-ref}
77
+ - Gemini CLI: /{{project}}:review-code {task-ref}
78
78
  - Codex CLI: $review-code {task-ref}
79
79
  - Commit directly (optional; only when all issues are resolved and changes are low risk):
80
80
  - Claude Code / OpenCode: /commit
81
- - Gemini CLI: /agent-infra:commit
81
+ - Gemini CLI: /{{project}}:commit
82
82
  - Codex CLI: $commit
83
83
  ```
84
84
 
@@ -74,11 +74,11 @@ manual-validation 项不在修复范围。处理规则:
74
74
  下一步 - 重新审查或提交:
75
75
  - 重新审查(始终推荐):
76
76
  - Claude Code / OpenCode:/review-code {task-ref}
77
- - Gemini CLI:/agent-infra:review-code {task-ref}
77
+ - Gemini CLI:/{{project}}:review-code {task-ref}
78
78
  - Codex CLI:$review-code {task-ref}
79
79
  - 直接提交(可选;仅在所有问题已解决且风险可控时):
80
80
  - Claude Code / OpenCode:/commit
81
- - Gemini CLI:/agent-infra:commit
81
+ - Gemini CLI:/{{project}}:commit
82
82
  - Codex CLI:$commit
83
83
  ```
84
84
 
@@ -15,6 +15,6 @@
15
15
 
16
16
  下一步 - 代码审查:
17
17
  - Claude Code / OpenCode:/review-code {task-ref}
18
- - Gemini CLI:/agent-infra:review-code {task-ref}
18
+ - Gemini CLI:/{{project}}:review-code {task-ref}
19
19
  - Codex CLI:$review-code {task-ref}
20
20
  ```