@fitlab-ai/agent-infra 0.8.4 → 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.
- package/dist/lib/defaults.json +8 -0
- package/dist/lib/init.js +5 -0
- package/dist/lib/sandbox/commands/create.js +30 -4
- package/dist/lib/sandbox/engine.js +33 -3
- package/dist/lib/sandbox/engines/colima.js +2 -2
- package/dist/lib/sandbox/runtimes/base.dockerfile +4 -0
- package/dist/lib/server/daemon.js +9 -0
- package/dist/lib/server/process-control.js +66 -73
- package/dist/lib/server/process-state.js +168 -0
- package/dist/lib/update.js +2 -0
- package/lib/defaults.json +8 -0
- package/lib/init.ts +9 -0
- package/lib/sandbox/commands/create.ts +32 -3
- package/lib/sandbox/engine.ts +37 -3
- package/lib/sandbox/engines/colima.ts +2 -2
- package/lib/sandbox/runtimes/base.dockerfile +4 -0
- package/lib/server/daemon.ts +9 -0
- package/lib/server/process-control.ts +70 -71
- package/lib/server/process-state.ts +183 -0
- package/lib/update.ts +6 -0
- package/package.json +2 -1
- package/runtime/platform-adapters/platform-sync.github.js +1501 -0
- package/templates/.agents/README.en.md +2 -0
- package/templates/.agents/README.zh-CN.md +2 -0
- package/templates/.agents/rules/review-handshake.en.md +5 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +5 -4
- package/templates/.agents/scripts/lib/agent-infra-package.js +149 -0
- package/templates/.agents/scripts/lib/post-review-commit.js +5 -0
- package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +25 -1459
- package/templates/.agents/scripts/review-diff-fingerprint.js +95 -24
- package/templates/.agents/scripts/validate-artifact.js +68 -55
- package/templates/.agents/skills/commit/SKILL.en.md +5 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +29 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +29 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/init-milestones/SKILL.en.md +2 -2
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +5 -4
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +2 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -1
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +174 -136
|
@@ -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
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
82
|
-
return
|
|
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,
|
|
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
|
|
94
|
-
|
|
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));
|
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import {
|
|
8
8
|
extractReviewBaseline,
|
|
9
9
|
extractReviewDiffFingerprint,
|
|
10
|
+
extractReviewedSnapshotTree,
|
|
10
11
|
findAuthoritativeReviewCodeArtifact,
|
|
11
12
|
parseReviewVerdict,
|
|
12
13
|
resolvePostReviewGlobs
|
|
@@ -779,12 +780,10 @@ function checkPostReviewCommit({ taskDir, config }) {
|
|
|
779
780
|
|
|
780
781
|
const task = loadTask(taskDir);
|
|
781
782
|
const content = fs.readFileSync(reviewArtifact.path, "utf8");
|
|
782
|
-
const reviewBaseline = extractReviewBaseline(content);
|
|
783
783
|
const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
|
|
784
784
|
const baselineSource = resolvePostReviewBaseline({
|
|
785
785
|
gitRoot,
|
|
786
786
|
lastReviewedCommit,
|
|
787
|
-
reviewBaseline,
|
|
788
787
|
reviewArtifact: reviewArtifact.fileName
|
|
789
788
|
});
|
|
790
789
|
if (!baselineSource.ok) {
|
|
@@ -818,7 +817,7 @@ function checkPostReviewCommit({ taskDir, config }) {
|
|
|
818
817
|
|
|
819
818
|
return failResult(
|
|
820
819
|
"post-review-commit",
|
|
821
|
-
`${commits.length} commit(s) to code/rule paths after
|
|
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`
|
|
822
821
|
);
|
|
823
822
|
}
|
|
824
823
|
|
|
@@ -841,6 +840,7 @@ function checkReviewFact({ taskDir, artifactFile }) {
|
|
|
841
840
|
const verdict = parseReviewVerdict(content);
|
|
842
841
|
const reviewBaseline = extractReviewBaseline(content);
|
|
843
842
|
const reviewedFingerprint = extractReviewDiffFingerprint(content);
|
|
843
|
+
const reviewedTree = extractReviewedSnapshotTree(content);
|
|
844
844
|
|
|
845
845
|
if (!["通过", "需要修改", "拒绝", "Approved", "Changes Requested", "Rejected"].includes(verdict)) {
|
|
846
846
|
return failResult("review-fact", `Unsupported review verdict '${verdict}'`);
|
|
@@ -867,13 +867,13 @@ function checkReviewFact({ taskDir, artifactFile }) {
|
|
|
867
867
|
);
|
|
868
868
|
}
|
|
869
869
|
|
|
870
|
-
let
|
|
870
|
+
let actualSnapshot;
|
|
871
871
|
try {
|
|
872
|
-
|
|
872
|
+
actualSnapshot = JSON.parse(execFileSync(
|
|
873
873
|
process.execPath,
|
|
874
|
-
[path.join(repoRoot, ".agents", "scripts", "review-diff-fingerprint.js"), "worktree", baseline],
|
|
874
|
+
[path.join(repoRoot, ".agents", "scripts", "review-diff-fingerprint.js"), "worktree", baseline, "--format", "json"],
|
|
875
875
|
{ cwd: gitRoot, encoding: "utf8" }
|
|
876
|
-
)
|
|
876
|
+
));
|
|
877
877
|
} catch {
|
|
878
878
|
return blockedResult(
|
|
879
879
|
"review-fact",
|
|
@@ -881,26 +881,37 @@ function checkReviewFact({ taskDir, artifactFile }) {
|
|
|
881
881
|
);
|
|
882
882
|
}
|
|
883
883
|
|
|
884
|
-
if (
|
|
884
|
+
if (actualSnapshot.fingerprint !== reviewedFingerprint) {
|
|
885
885
|
return failResult(
|
|
886
886
|
"review-fact",
|
|
887
887
|
`Reviewed diff fingerprint does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`
|
|
888
888
|
);
|
|
889
889
|
}
|
|
890
890
|
|
|
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
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
891
898
|
if (["通过", "Approved"].includes(verdict)) {
|
|
892
899
|
const lastReviewedCommit = String(task.metadata.last_reviewed_commit || "").trim();
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
+
);
|
|
898
910
|
}
|
|
899
|
-
|
|
900
|
-
if (reviewedCommit !== baseline) {
|
|
911
|
+
if (!cleanSnapshot && lastReviewedCommit) {
|
|
901
912
|
return failResult(
|
|
902
913
|
"review-fact",
|
|
903
|
-
|
|
914
|
+
"Approved review with uncommitted changes must remain unanchored until commit"
|
|
904
915
|
);
|
|
905
916
|
}
|
|
906
917
|
}
|
|
@@ -911,35 +922,18 @@ function checkReviewFact({ taskDir, artifactFile }) {
|
|
|
911
922
|
);
|
|
912
923
|
}
|
|
913
924
|
|
|
914
|
-
function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit,
|
|
915
|
-
if (lastReviewedCommit) {
|
|
916
|
-
|
|
917
|
-
return { ok: true, sha: lastReviewedCommit };
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
if (!reviewBaseline) {
|
|
922
|
-
return {
|
|
923
|
-
ok: false,
|
|
924
|
-
result: passResult(
|
|
925
|
-
"post-review-commit",
|
|
926
|
-
`${reviewArtifact} predates baseline-commit anchoring; skipped (legacy artifact)`,
|
|
927
|
-
[`${reviewArtifact} has no 审查基线提交 / Review Baseline Commit field`]
|
|
928
|
-
)
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (!SHA_PATTERN.test(reviewBaseline)) {
|
|
933
|
-
return {
|
|
934
|
-
ok: false,
|
|
935
|
-
result: blockedResult(
|
|
936
|
-
"post-review-commit",
|
|
937
|
-
`${reviewArtifact} has an empty or malformed 审查基线提交 SHA ('${reviewBaseline}'); manual remediation required`
|
|
938
|
-
)
|
|
939
|
-
};
|
|
925
|
+
function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
|
|
926
|
+
if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
|
|
927
|
+
return { ok: true, sha: lastReviewedCommit };
|
|
940
928
|
}
|
|
941
929
|
|
|
942
|
-
return {
|
|
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
|
+
};
|
|
943
937
|
}
|
|
944
938
|
|
|
945
939
|
function gitCommitExists(gitRoot, sha) {
|
|
@@ -1035,22 +1029,41 @@ function parseFrontmatter(content) {
|
|
|
1035
1029
|
function getSectionContent(content, names) {
|
|
1036
1030
|
const lines = content.split(/\r?\n/);
|
|
1037
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
|
+
|
|
1038
1058
|
for (const name of names) {
|
|
1039
1059
|
const heading = `## ${name}`;
|
|
1040
|
-
const
|
|
1041
|
-
if (
|
|
1060
|
+
const position = headings.findIndex((item) => item.text === heading);
|
|
1061
|
+
if (position === -1) {
|
|
1042
1062
|
continue;
|
|
1043
1063
|
}
|
|
1044
|
-
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
if (lines[index].startsWith("## ")) {
|
|
1048
|
-
break;
|
|
1049
|
-
}
|
|
1050
|
-
sectionLines.push(lines[index]);
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
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();
|
|
1054
1067
|
}
|
|
1055
1068
|
|
|
1056
1069
|
return "";
|
|
@@ -61,9 +61,11 @@ Review status, diff, and recent history, then prepare a Conventional Commit with
|
|
|
61
61
|
Stage specific files only and run `git commit` with the prepared message.
|
|
62
62
|
|
|
63
63
|
If this commit is associated with a task and a `review-code` artifact exists, read the highest-round `review-code` artifact before committing:
|
|
64
|
-
- If that artifact's `Overall Verdict` / `总体结论` is Approved, parse `
|
|
65
|
-
- After staging the explicit files,
|
|
66
|
-
-
|
|
64
|
+
- If that artifact's `Overall Verdict` / `总体结论` is Approved, parse `R`, `F`, and `Reviewed Snapshot Tree` / `审查快照树` (`T`)
|
|
65
|
+
- After staging the explicit files, record `pre_head=$(git rev-parse HEAD)` and use the helper's JSON mode to generate the complete worktree tree `W` and normalized staged tree `S`
|
|
66
|
+
- Before `git commit`, require `pre_head == R && W == T && S == T`; use the helper's `compare` mode to produce added, missing, and different diagnostics for both worktree and staged snapshots
|
|
67
|
+
- If any condition fails, enter Scenario 4 in `reference/task-status-update.md`; do not run `git commit`, push, successful state updates, PR summary sync, or the completion gate
|
|
68
|
+
- After all comparisons match and the commit succeeds, write `last_reviewed_commit: <new_head>` to task.md frontmatter
|
|
67
69
|
- Do not scan backward to earlier Approved artifacts; the highest-round `review-code` artifact is the only authoritative source
|
|
68
70
|
|
|
69
71
|
## 5. Push to the Existing PR When Applicable
|
|
@@ -61,9 +61,11 @@ git diff
|
|
|
61
61
|
只暂存明确列出的文件,然后执行 `git commit`。
|
|
62
62
|
|
|
63
63
|
如果本次提交关联任务且存在 `review-code` 产物,在提交前读取最高轮 `review-code` 产物:
|
|
64
|
-
- 若该产物 `总体结论` / `Overall Verdict` 为 Approved,解析
|
|
65
|
-
-
|
|
66
|
-
-
|
|
64
|
+
- 若该产物 `总体结论` / `Overall Verdict` 为 Approved,解析 `R`、`F` 与 `审查快照树` / `Reviewed Snapshot Tree`(`T`)
|
|
65
|
+
- 暂存明确文件后记录 `pre_head=$(git rev-parse HEAD)`,并以 helper 的 JSON 模式生成当前完整工作区树 `W` 与规范化暂存树 `S`
|
|
66
|
+
- 在 `git commit` 前要求 `pre_head == R && W == T && S == T`;分别运行 helper 的 `compare` 模式生成 worktree/staged 的 added、missing、different 路径诊断
|
|
67
|
+
- 任一条件不满足时进入 `reference/task-status-update.md` 的“场景 4:提交前快照阻断”,不得执行 `git commit`、push、成功状态更新、PR 摘要同步或完成 gate
|
|
68
|
+
- 全部相等并成功提交后,在 task.md frontmatter 写入 `last_reviewed_commit: <new_head>`
|
|
67
69
|
- 不向后扫描更早的 Approved 产物;最高轮 `review-code` 产物是唯一权威来源
|
|
68
70
|
|
|
69
71
|
## 5. 推送到已有 PR(按需)
|
|
@@ -18,13 +18,40 @@ For every task-related commit, append this Activity Log entry in `task.md`:
|
|
|
18
18
|
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Commit** by {agent} — {commit hash short} {commit subject}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
If the commit stage confirmed that the highest-round `review-code` artifact is Approved, `pre_head
|
|
21
|
+
If the commit stage confirmed that the highest-round `review-code` artifact is Approved, `pre_head == R`, complete worktree tree `W == T`, and normalized staged tree `S == T`, also write or refresh after a successful commit:
|
|
22
22
|
|
|
23
23
|
```yaml
|
|
24
24
|
last_reviewed_commit: {new_head}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
This field is the
|
|
27
|
+
This field is the only baseline for the `complete-task` `post-review-commit` gate. When any condition is not met, do not write or advance it.
|
|
28
|
+
|
|
29
|
+
### Scenario 4: pre-commit snapshot block
|
|
30
|
+
|
|
31
|
+
This scenario ends the run before `git commit` and does not enter the successful post-commit scenario selection below. When any of `pre_head != R`, `W != T`, or `S != T` applies:
|
|
32
|
+
|
|
33
|
+
- Do not run `git commit`, push, successful state updates, Issue/PR success sync, or the commit completion gate; preserve the current worktree and index.
|
|
34
|
+
- Refresh task `updated_at`, `assigned_to`, and `agent_infra_version`, then append a done log with action `Commit`: `Blocked before git commit: reviewed snapshot mismatch (worktree added={a}, missing={m}, different={d}; staged added={a}, missing={m}, different={d})`.
|
|
35
|
+
- User output must include `No commit was created.` and show Added/Missing/Different under both `Current worktree vs reviewed snapshot` and `Staged snapshot vs reviewed snapshot`; render an empty set as `- (none)` and list paths only in user output.
|
|
36
|
+
- When `pre_head != R` or `W != T`, the only next step is a fresh `review-code`; when only `W == T && S != T`, tell the user to fix staging and rerun `commit` without re-review.
|
|
37
|
+
|
|
38
|
+
Full re-review commands:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Next step - re-run code review:
|
|
42
|
+
- Claude Code / OpenCode: /review-code {task-ref}
|
|
43
|
+
- Gemini CLI: /{{project}}:review-code {task-ref}
|
|
44
|
+
- Codex CLI: $review-code {task-ref}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Full staging-only retry commands:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
Next step - fix staging and retry commit:
|
|
51
|
+
- Claude Code / OpenCode: /commit {task-ref}
|
|
52
|
+
- Gemini CLI: /{{project}}:commit {task-ref}
|
|
53
|
+
- Codex CLI: $commit {task-ref}
|
|
54
|
+
```
|
|
28
55
|
|
|
29
56
|
Before selecting the next step, verify:
|
|
30
57
|
- `current_step` and the latest workflow progress in `task.md`
|
|
@@ -18,13 +18,40 @@ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
|
|
|
18
18
|
- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Commit** by {agent} — {commit hash short} {commit subject}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
如果提交阶段已确认最高轮 `review-code` 产物 Approved、`pre_head
|
|
21
|
+
如果提交阶段已确认最高轮 `review-code` 产物 Approved、`pre_head == R`、完整工作区树 `W == T` 且规范化暂存树 `S == T`,成功提交后同时写入或刷新:
|
|
22
22
|
|
|
23
23
|
```yaml
|
|
24
24
|
last_reviewed_commit: {new_head}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
该字段是 `complete-task` 的 `post-review-commit` gate
|
|
27
|
+
该字段是 `complete-task` 的 `post-review-commit` gate 唯一 baseline。条件不满足时不要写入或推进该字段。
|
|
28
|
+
|
|
29
|
+
### 场景 4:提交前快照阻断
|
|
30
|
+
|
|
31
|
+
此场景在 `git commit` 前终止本轮,不进入下方提交成功后的场景选择。任一 `pre_head != R`、`W != T` 或 `S != T` 命中时:
|
|
32
|
+
|
|
33
|
+
- 不执行 `git commit`、push、成功状态更新、Issue/PR 成功同步或 commit 完成 gate;保留当前工作区与暂存区。
|
|
34
|
+
- 刷新任务的 `updated_at`、`assigned_to`、`agent_infra_version`,并以 action `Commit` 追加 done 日志:`Blocked before git commit: reviewed snapshot mismatch (worktree added={a}, missing={m}, different={d}; staged added={a}, missing={m}, different={d})`。
|
|
35
|
+
- 用户输出必须包含 `No commit was created.`,并分别按 `Current worktree vs reviewed snapshot`、`Staged snapshot vs reviewed snapshot` 展示 Added/Missing/Different;空集合显示 `- (none)`,路径只在用户输出中逐行展示。
|
|
36
|
+
- `pre_head != R` 或 `W != T` 时,下一步只指向重新 `review-code`;仅 `W == T && S != T` 时,提示修正暂存后重跑 `commit`,无需重审。
|
|
37
|
+
|
|
38
|
+
重新审查的完整命令:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
下一步 - 重新代码审查:
|
|
42
|
+
- Claude Code / OpenCode: /review-code {task-ref}
|
|
43
|
+
- Gemini CLI: /{{project}}:review-code {task-ref}
|
|
44
|
+
- Codex CLI: $review-code {task-ref}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
仅修正暂存的完整命令:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
下一步 - 修正暂存后重新提交:
|
|
51
|
+
- Claude Code / OpenCode: /commit {task-ref}
|
|
52
|
+
- Gemini CLI: /{{project}}:commit {task-ref}
|
|
53
|
+
- Codex CLI: $commit {task-ref}
|
|
54
|
+
```
|
|
28
55
|
|
|
29
56
|
在决定下一步之前,先确认:
|
|
30
57
|
- `task.md` 中的 `current_step` 和最新工作流进度
|
|
@@ -98,7 +98,7 @@ node .agents/scripts/validate-artifact.js check review-ledger .agents/workspace/
|
|
|
98
98
|
node .agents/scripts/validate-artifact.js check post-review-commit .agents/workspace/active/{task-id} --skill complete-task --format text
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
A non-zero exit from either (fail/blocked) -> treat as an unmet prerequisite and **stop**, do not run Steps 3-7. `--force` does **NOT** lift this hard gate: unclosed disagreements must first be closed in the ledger (`confirmed`/`closed`/`human-decided`), and un-re-reviewed
|
|
101
|
+
A non-zero exit from either (fail/blocked) -> treat as an unmet prerequisite and **stop**, do not run Steps 3-7. If the output contains `reviewed snapshot was not anchored`, rerun `commit` or `review-code`; never fall back to the review baseline. `--force` does **NOT** lift this hard gate: unclosed disagreements must first be closed in the ledger (`confirmed`/`closed`/`human-decided`), and un-re-reviewed commits after an anchor must be re-reviewed via `review-code` or covered by a `post-review-commit` / `human-decided` exemption row in the ledger.
|
|
102
102
|
|
|
103
103
|
> **⚠️ Prerequisite Branch Check — you must decide whether to continue or stop before proceeding:**
|
|
104
104
|
>
|
|
@@ -166,7 +166,7 @@ Check whether `task.md` includes a valid `issue_number`. If not, skip this step
|
|
|
166
166
|
If a valid `issue_number` exists:
|
|
167
167
|
- First scan and backfill unpublished `task.md`, `analysis*.md`, `review-analysis*.md`, `plan*.md`, `review-plan*.md`, `code*.md`, and `review-code*.md` comments using the backfill rules in `.agents/rules/issue-sync.md` (`task.md` uses the idempotent update path)
|
|
168
168
|
- Backfill checked `## Requirements` items to the Issue body by following the requirements-checkbox sync steps in issue-sync.md
|
|
169
|
-
- Do not set any `status:` label — status labels
|
|
169
|
+
- Do not set any `status:` label — platform automation should clear status labels after the Issue closes; the pre-completion platform-sync gate verifies that a CLOSED Issue has no `status:` labels and fails until the workflow finishes or is repaired
|
|
170
170
|
- Finally create or update the summary comment marked with the summary marker defined in `.agents/rules/issue-sync.md`
|
|
171
171
|
- Read `.agents/rules/issue-fields.md` and follow Flow A to sync every non-empty Issue field (`priority`/`effort`/`start_date`/`target_date`) from `task.md` to the Issue (idempotent; skip without blocking when `has_push=false` or the fetch/write fails)
|
|
172
172
|
|
|
@@ -97,7 +97,7 @@ node .agents/scripts/validate-artifact.js check review-ledger .agents/workspace/
|
|
|
97
97
|
node .agents/scripts/validate-artifact.js check post-review-commit .agents/workspace/active/{task-id} --skill complete-task --format text
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
任一退出码非 0(fail/blocked)→ 按前置条件未满足处理,**停止**,不执行步骤 3-7
|
|
100
|
+
任一退出码非 0(fail/blocked)→ 按前置条件未满足处理,**停止**,不执行步骤 3-7。若输出包含 `reviewed snapshot was not anchored`,必须先重新 `commit` 或 `review-code`;不得回退审查基线。`--force` **不解除**本硬门禁:未关闭分歧必须先在账本闭合(`confirmed`/`closed`/`human-decided`),已锚点后的未复审提交必须重新 `review-code` 或在账本追加 `post-review-commit` / `human-decided` 豁免行。
|
|
101
101
|
|
|
102
102
|
> **⚠️ 前置条件分支判断 — 你必须先判断“继续”还是“停止”:**
|
|
103
103
|
>
|
|
@@ -165,7 +165,7 @@ ls .agents/workspace/completed/{task-id}/task.md
|
|
|
165
165
|
如果存在有效的 `issue_number`:
|
|
166
166
|
- 先按 `.agents/rules/issue-sync.md` 的补发规则扫描并补发未发布的 `task.md`、`analysis*.md`、`review-analysis*.md`、`plan*.md`、`review-plan*.md`、`code*.md`、`review-code*.md` 评论(`task.md` 走幂等更新路径)
|
|
167
167
|
- 按 issue-sync.md 的需求复选框同步步骤,兜底同步 `## 需求` 中已勾选的条目到 Issue body
|
|
168
|
-
- 不要设置 `status:` label — Issue
|
|
168
|
+
- 不要设置 `status:` label — 平台自动化应在 Issue 关闭后清理状态标签;预完成 platform-sync gate 会验证 CLOSED Issue 不含任何 `status:` 标签,残留时失败并要求等待或修复 workflow 后重跑
|
|
169
169
|
- 最后创建或更新 `.agents/rules/issue-sync.md` 中定义的 summary 评论标记对应的 summary 评论
|
|
170
170
|
- 读取 `.agents/rules/issue-fields.md`,按流程 A 把 `task.md` 中所有非空的 Issue 字段(`priority`/`effort`/`start_date`/`target_date`)同步到 Issue(幂等;`has_push=false` 或取数/写入失败时跳过,不阻断)
|
|
171
171
|
|
|
@@ -40,7 +40,7 @@ The script and `.agents/rules/label-milestone-setup.md` are responsible for:
|
|
|
40
40
|
Create the following milestones with fixed descriptions:
|
|
41
41
|
- `General Backlog`: `All unsorted backlogged tasks may be completed in a future version.` (state=`open`)
|
|
42
42
|
- `{major}.{minor}.x`: `Issues that we want to resolve in {major}.{minor} line.` (state=`open`)
|
|
43
|
-
- `{major}.{minor}.{patch+1}
|
|
43
|
+
- Release version: use compatibility baseline `0.1.0` for the default source, or `{major}.{minor}.{patch+1}` for a valid tag source. The description is `Issues that we want to release in v{version}.` (state=`open`)
|
|
44
44
|
|
|
45
45
|
When `--history` is present, each historical `vX.Y.Z` tag additionally contributes:
|
|
46
46
|
- `X.Y.x` as an open line milestone
|
|
@@ -59,7 +59,7 @@ The summary must include:
|
|
|
59
59
|
Operational notes:
|
|
60
60
|
- Milestone titles are treated as the idempotency key.
|
|
61
61
|
- General Backlog is the fallback milestone for unsorted work.
|
|
62
|
-
- Without `--history`,
|
|
62
|
+
- Without `--history`, create one standard release milestone: compatibility baseline `0.1.0` for the default source, or the next patch version for a valid tag source.
|
|
63
63
|
- Historical `X.Y.Z` tags create `X.Y.x` milestones as open and `X.Y.Z` milestones as closed.
|
|
64
64
|
- Repositories with many tags may hit the platform API rate limit.
|
|
65
65
|
|
|
@@ -40,7 +40,7 @@ bash .agents/skills/init-milestones/scripts/init-milestones.sh "$ARGUMENTS"
|
|
|
40
40
|
按固定描述创建以下里程碑:
|
|
41
41
|
- `General Backlog`:`All unsorted backlogged tasks may be completed in a future version.`(state=`open`)
|
|
42
42
|
- `{major}.{minor}.x`:`Issues that we want to resolve in {major}.{minor} line.`(state=`open`)
|
|
43
|
-
- `{major}.{minor}.{patch+1}
|
|
43
|
+
- 具体版本:兼容默认来源使用基线 `0.1.0`;合法 tag 来源使用 `{major}.{minor}.{patch+1}`。描述为 `Issues that we want to release in v{version}.`(state=`open`)
|
|
44
44
|
|
|
45
45
|
当传入 `--history` 时,每个历史 `vX.Y.Z` tag 还会额外贡献:
|
|
46
46
|
- `X.Y.x` 作为开启状态的线里程碑
|
|
@@ -59,7 +59,7 @@ bash .agents/skills/init-milestones/scripts/init-milestones.sh "$ARGUMENTS"
|
|
|
59
59
|
执行说明:
|
|
60
60
|
- Milestone titles are treated as the idempotency key.
|
|
61
61
|
- General Backlog 是未分类工作的兜底里程碑。
|
|
62
|
-
- 不带 `--history`
|
|
62
|
+
- 不带 `--history` 时,只创建一个标准版本里程碑:兼容默认来源使用基线 `0.1.0`,合法 tag 来源使用下一次 patch 版本。
|
|
63
63
|
- 历史 `X.Y.Z` tag 会生成开启状态的 `X.Y.x` 和关闭状态的 `X.Y.Z`。
|
|
64
64
|
- 标签较多的仓库可能触发平台 API rate limit。
|
|
65
65
|
|
|
@@ -189,17 +189,18 @@ IFS="$previous_ifs"
|
|
|
189
189
|
current_version="$major.$minor.$patch"
|
|
190
190
|
if [ "$selected_tag" = "-" ]; then
|
|
191
191
|
version_source="compatibility default"
|
|
192
|
+
version_milestone="$current_version"
|
|
192
193
|
else
|
|
193
194
|
version_source="git tag $selected_tag"
|
|
195
|
+
version_milestone="$major.$minor.$next_patch"
|
|
194
196
|
fi
|
|
195
197
|
|
|
196
198
|
line_milestone="$major.$minor.x"
|
|
197
|
-
next_version="$major.$minor.$next_patch"
|
|
198
199
|
|
|
199
200
|
echo "Detected version baseline: $current_version"
|
|
200
201
|
echo "Version baseline source: $version_source"
|
|
201
202
|
echo "Line milestone: $line_milestone"
|
|
202
|
-
echo "Next version milestone: $
|
|
203
|
+
echo "Next version milestone: $version_milestone"
|
|
203
204
|
|
|
204
205
|
repo="$(gh repo view --json nameWithOwner --jq '.nameWithOwner')"
|
|
205
206
|
|
|
@@ -213,7 +214,7 @@ cat "$tmpdir/existing.tsv"
|
|
|
213
214
|
cat <<EOF > "$tmpdir/desired.tsv"
|
|
214
215
|
General Backlog All unsorted backlogged tasks may be completed in a future version. open
|
|
215
216
|
$line_milestone Issues that we want to resolve in $major.$minor line. open
|
|
216
|
-
$
|
|
217
|
+
$version_milestone Issues that we want to release in v$version_milestone. open
|
|
217
218
|
EOF
|
|
218
219
|
|
|
219
220
|
if [ "$history_mode" = "true" ]; then
|
|
@@ -285,7 +286,7 @@ echo
|
|
|
285
286
|
echo "Notes:"
|
|
286
287
|
echo "- Milestone titles are treated as the idempotency key."
|
|
287
288
|
echo "- General Backlog is the fallback milestone for unsorted work."
|
|
288
|
-
echo "- Without --history, version
|
|
289
|
+
echo "- Without --history, a single version milestone is created based on the detected baseline."
|
|
289
290
|
|
|
290
291
|
if [ "$history_mode" = "true" ]; then
|
|
291
292
|
echo "- Historical X.Y.Z tags create X.Y.x milestones as open and X.Y.Z milestones as closed."
|
|
@@ -77,7 +77,7 @@ Follow `.agents/workflows/feature-development.yaml` and inspect the full change
|
|
|
77
77
|
- Capture `R=$(git rev-parse HEAD)` exactly once; reuse that R for this round's report, fingerprint, and task review fact, and do not re-read HEAD later as a substitute
|
|
78
78
|
- `git diff --binary "$R" -- <post-review-globs>` for tracked changes
|
|
79
79
|
- `git ls-files -o --exclude-standard -z -- <post-review-globs>` for untracked new files
|
|
80
|
-
- `node .agents/scripts/review-diff-fingerprint.js worktree "$R"`
|
|
80
|
+
- `node .agents/scripts/review-diff-fingerprint.js worktree "$R" --format json` to generate the reviewed diff fingerprint `F` and reviewed snapshot tree `T` together; write both into the report
|
|
81
81
|
|
|
82
82
|
> Detailed review criteria, severity rules, and reviewer expectations live in `reference/review-criteria.md`. Read `reference/review-criteria.md` before reviewing.
|
|
83
83
|
> Test review gate: when `git diff` touches test files, read `.agents/rules/testing-discipline.md` first and check it item by item, especially "do not add negative assertions when a positive assertion already covers the behavior".
|
|
@@ -97,7 +97,7 @@ date "+%Y-%m-%d %H:%M:%S%z" | sed 's/\([+-][0-9][0-9]\)\([0-9][0-9]\)$/\1:\2/'
|
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
Update task.md:
|
|
100
|
-
- When this round's `Overall Verdict` / `总体结论` is `Approved` /
|
|
100
|
+
- When this round's `Overall Verdict` / `总体结论` is `Approved` / `通过` and `T == R^{tree}`, write `last_reviewed_commit: {R}`; when the Approved snapshot contains uncommitted changes, clear any existing `last_reviewed_commit` until `commit` anchors it
|
|
101
101
|
- When this round is not Approved, preserve the existing `last_reviewed_commit`; do not advance or clear it
|
|
102
102
|
- Append:
|
|
103
103
|
`- {YYYY-MM-DD HH:mm:ss±HH:MM} — **Review Code (Round {N})** by {agent} — Verdict: {Approved/Changes Requested/Rejected}, blockers: {n}, major: {n}, minor: {n}, Manual-validation: {n} → {artifact-filename}`
|