@fitlab-ai/agent-infra 0.9.0 → 0.9.1
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/README.md +6 -1
- package/README.zh-CN.md +5 -1
- package/dist/bin/internal-cli.js +5 -0
- package/dist/lib/internal/platform-release-notes.js +53 -0
- package/dist/lib/internal/release-workflow.js +105 -2
- package/dist/lib/platform/github-release-notes.js +96 -0
- package/dist/lib/platform/issues.js +4 -2
- package/dist/lib/platform/merged-pr-equivalence.js +54 -0
- package/dist/lib/platform/pr-checks.js +6 -6
- package/dist/lib/platform/pull-requests.js +3 -1
- package/dist/lib/platform/release-notes.js +143 -0
- package/dist/lib/platform/verification-required.js +29 -5
- package/dist/lib/sandbox/build-proxy.js +63 -0
- package/dist/lib/sandbox/commands/create.js +57 -9
- package/dist/lib/sandbox/commands/rebuild.js +24 -7
- package/dist/lib/sandbox/image-build.js +2 -1
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/dist/lib/sandbox/shell.js +2 -1
- package/dist/lib/task/commands/issue-body.js +10 -5
- package/dist/lib/task/events.js +1 -1
- package/dist/lib/task/issue-form.js +2 -0
- package/dist/lib/task/review-fingerprint.js +9 -0
- package/dist/lib/task/verification-engine.js +34 -17
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +1 -1
- package/lib/internal/platform-release-notes.ts +55 -0
- package/lib/internal/release-workflow.ts +105 -3
- package/lib/platform/github-release-notes.ts +132 -0
- package/lib/platform/issues.ts +6 -4
- package/lib/platform/merged-pr-equivalence.ts +70 -0
- package/lib/platform/pr-checks.ts +8 -7
- package/lib/platform/pull-requests.ts +9 -3
- package/lib/platform/release-notes.ts +167 -0
- package/lib/platform/verification-required.ts +31 -5
- package/lib/sandbox/build-proxy.ts +94 -0
- package/lib/sandbox/commands/create.ts +77 -11
- package/lib/sandbox/commands/rebuild.ts +29 -7
- package/lib/sandbox/image-build.ts +4 -1
- package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/lib/sandbox/shell.ts +3 -1
- package/lib/task/commands/issue-body.ts +10 -5
- package/lib/task/events.ts +1 -1
- package/lib/task/issue-form.ts +4 -0
- package/lib/task/review-fingerprint.ts +10 -1
- package/lib/task/verification-engine.ts +33 -17
- package/lib/task/verification-review.ts +1 -0
- package/lib/task/verification.ts +1 -1
- package/package.json +1 -1
- package/templates/.agents/rules/release-commands.en.md +2 -2
- package/templates/.agents/rules/release-commands.github.en.md +10 -40
- package/templates/.agents/rules/release-commands.github.zh-CN.md +9 -39
- package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
- package/templates/.agents/rules/review-handshake.en.md +2 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +2 -1
- package/templates/.agents/rules/testing-discipline.en.md +20 -2
- package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -38
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
- package/templates/.agents/templates/task.en.md +18 -0
- package/templates/.agents/templates/task.zh-CN.md +18 -0
|
@@ -5,20 +5,41 @@ ENV NPM_CONFIG_PREFIX=/home/devuser/.npm-global
|
|
|
5
5
|
ENV PATH="/home/devuser/.npm-global/bin:${PATH}"
|
|
6
6
|
|
|
7
7
|
ARG AI_TOOL_PACKAGES=
|
|
8
|
-
RUN
|
|
8
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
9
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
10
|
+
--mount=type=secret,id=NO_PROXY \
|
|
11
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
12
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
13
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
14
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
15
|
+
set -e && \
|
|
9
16
|
for pkg in ${AI_TOOL_PACKAGES}; do \
|
|
10
17
|
npm install -g "$pkg"; \
|
|
11
18
|
done
|
|
12
19
|
|
|
13
20
|
ARG AI_TOOLS_SHELL_INSTALL_B64=
|
|
14
|
-
RUN
|
|
21
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
22
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
23
|
+
--mount=type=secret,id=NO_PROXY \
|
|
24
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
25
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
26
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
27
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
28
|
+
if [ -n "${AI_TOOLS_SHELL_INSTALL_B64}" ]; then \
|
|
15
29
|
set -e && \
|
|
16
30
|
echo "${AI_TOOLS_SHELL_INSTALL_B64}" | base64 -d > /tmp/ai-tools-install.sh && \
|
|
17
31
|
bash /tmp/ai-tools-install.sh && \
|
|
18
32
|
rm /tmp/ai-tools-install.sh; \
|
|
19
33
|
fi
|
|
20
34
|
|
|
21
|
-
RUN
|
|
35
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
36
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
37
|
+
--mount=type=secret,id=NO_PROXY \
|
|
38
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
39
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
40
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
41
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
42
|
+
npm install -g pyright
|
|
22
43
|
|
|
23
44
|
RUN mkdir -p /home/devuser/.local/share /home/devuser/.local/state
|
|
24
45
|
|
|
@@ -19,7 +19,14 @@ RUN if [ "${HOST_UID}" = "0" ]; then \
|
|
|
19
19
|
useradd -u ${HOST_UID} -g ${HOST_GID} -m -s /bin/bash devuser; \
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
|
-
RUN
|
|
22
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
23
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
24
|
+
--mount=type=secret,id=NO_PROXY \
|
|
25
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
26
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
27
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
28
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
29
|
+
apt-get update && apt-get install -y \
|
|
23
30
|
curl wget git vim file jq \
|
|
24
31
|
build-essential ca-certificates gnupg lsb-release \
|
|
25
32
|
libevent-core-2.1-7 libncursesw6 libtinfo6 \
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
RUN
|
|
1
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
2
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
3
|
+
--mount=type=secret,id=NO_PROXY \
|
|
4
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
5
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
6
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
7
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
8
|
+
apt-get update && apt-get install -y \
|
|
2
9
|
openjdk-17-jdk maven \
|
|
3
10
|
&& rm -rf /var/lib/apt/lists/*
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
RUN
|
|
1
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
2
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
3
|
+
--mount=type=secret,id=NO_PROXY \
|
|
4
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
5
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
6
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
7
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
8
|
+
apt-get update && apt-get install -y \
|
|
2
9
|
openjdk-21-jdk maven \
|
|
3
10
|
&& rm -rf /var/lib/apt/lists/*
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
RUN
|
|
1
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
2
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
3
|
+
--mount=type=secret,id=NO_PROXY \
|
|
4
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
5
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
6
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
7
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
8
|
+
bash -o pipefail -c 'curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors https://deb.nodesource.com/setup_20.x | bash -' && \
|
|
2
9
|
apt-get install -y nodejs && \
|
|
3
10
|
rm -rf /var/lib/apt/lists/*
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
RUN
|
|
1
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
2
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
3
|
+
--mount=type=secret,id=NO_PROXY \
|
|
4
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
5
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
6
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
7
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
8
|
+
bash -o pipefail -c 'curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors https://deb.nodesource.com/setup_22.x | bash -' && \
|
|
2
9
|
apt-get install -y nodejs && \
|
|
3
10
|
rm -rf /var/lib/apt/lists/*
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
RUN
|
|
1
|
+
RUN --mount=type=secret,id=HTTP_PROXY \
|
|
2
|
+
--mount=type=secret,id=HTTPS_PROXY \
|
|
3
|
+
--mount=type=secret,id=NO_PROXY \
|
|
4
|
+
export HTTP_PROXY="$(cat /run/secrets/HTTP_PROXY 2>/dev/null || true)" && \
|
|
5
|
+
export HTTPS_PROXY="$(cat /run/secrets/HTTPS_PROXY 2>/dev/null || true)" && \
|
|
6
|
+
export NO_PROXY="$(cat /run/secrets/NO_PROXY 2>/dev/null || true)" && \
|
|
7
|
+
export http_proxy="${HTTP_PROXY}" https_proxy="${HTTPS_PROXY}" no_proxy="${NO_PROXY}" && \
|
|
8
|
+
apt-get update && apt-get install -y \
|
|
2
9
|
python3 python3-pip python3-venv \
|
|
3
10
|
&& rm -rf /var/lib/apt/lists/*
|
package/lib/sandbox/shell.ts
CHANGED
|
@@ -12,6 +12,7 @@ type CommandOptions = {
|
|
|
12
12
|
stdio?: StdioOptions;
|
|
13
13
|
timeout?: number;
|
|
14
14
|
shell?: boolean;
|
|
15
|
+
env?: NodeJS.ProcessEnv;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
type RunProbeOptions = CommandOptions & {
|
|
@@ -23,7 +24,8 @@ function normalizeOptions(opts: CommandOptions = {}, stdio: StdioOptions): Comma
|
|
|
23
24
|
cwd: opts.cwd,
|
|
24
25
|
encoding: opts.encoding,
|
|
25
26
|
stdio,
|
|
26
|
-
timeout: opts.timeout ?? DEFAULT_TIMEOUT_MS
|
|
27
|
+
timeout: opts.timeout ?? DEFAULT_TIMEOUT_MS,
|
|
28
|
+
env: opts.env
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -12,12 +12,13 @@ Print a deterministic Issue body extracted from a task's task.md.
|
|
|
12
12
|
Omit <ref> Resolve the unique active task for the current branch.
|
|
13
13
|
<ref> Bare numeric short id, or a full TASK-YYYYMMDD-HHMMSS id.
|
|
14
14
|
--template <path> Render the final body for the given GitHub Issue Form (scenario A);
|
|
15
|
-
without it, print the default '描述 + 需求' body (scenario B).
|
|
15
|
+
without it, print the default '任务输入 + 描述 + 需求' body (scenario B).
|
|
16
16
|
|
|
17
|
-
Only the task title, '## 描述' and '## 需求' sections are ever
|
|
18
|
-
task.md
|
|
17
|
+
Only the task title, '## 任务输入', '## 描述' and '## 需求' sections are ever
|
|
18
|
+
emitted; the rest of task.md is never written to the body.
|
|
19
19
|
`;
|
|
20
20
|
|
|
21
|
+
const TASK_INPUT_ALIASES = ['任务输入', 'Task Input'];
|
|
21
22
|
const DESCRIPTION_ALIASES = ['描述', 'Description'];
|
|
22
23
|
const REQUIREMENTS_ALIASES = ['需求', 'Requirements'];
|
|
23
24
|
|
|
@@ -26,18 +27,22 @@ const REQUIREMENTS_ALIASES = ['需求', 'Requirements'];
|
|
|
26
27
|
* task.md actually uses, with empty sections falling back to `N/A`.
|
|
27
28
|
*/
|
|
28
29
|
function buildDefaultBody(content: string): string {
|
|
30
|
+
const taskInputHeading = findSectionHeading(content, TASK_INPUT_ALIASES);
|
|
29
31
|
const descHeading = findSectionHeading(content, DESCRIPTION_ALIASES);
|
|
30
32
|
const reqHeading = findSectionHeading(content, REQUIREMENTS_ALIASES);
|
|
33
|
+
const taskInput = extractSection(content, TASK_INPUT_ALIASES) || PLACEHOLDER;
|
|
31
34
|
const description = extractSection(content, DESCRIPTION_ALIASES) || PLACEHOLDER;
|
|
32
35
|
const requirements = extractSection(content, REQUIREMENTS_ALIASES) || PLACEHOLDER;
|
|
33
|
-
return `## ${descHeading}\n\n${description}\n\n## ${reqHeading}\n\n${requirements}\n`;
|
|
36
|
+
return `## ${taskInputHeading}\n\n${taskInput}\n\n## ${descHeading}\n\n${description}\n\n## ${reqHeading}\n\n${requirements}\n`;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
function readTaskFields(content: string): TaskFields {
|
|
37
40
|
return {
|
|
38
41
|
title: extractTitle(content),
|
|
39
42
|
description: extractSection(content, DESCRIPTION_ALIASES),
|
|
40
|
-
requirements: extractSection(content, REQUIREMENTS_ALIASES)
|
|
43
|
+
requirements: extractSection(content, REQUIREMENTS_ALIASES),
|
|
44
|
+
taskInput: extractSection(content, TASK_INPUT_ALIASES),
|
|
45
|
+
taskInputHeading: findSectionHeading(content, TASK_INPUT_ALIASES)
|
|
41
46
|
};
|
|
42
47
|
}
|
|
43
48
|
|
package/lib/task/events.ts
CHANGED
|
@@ -109,7 +109,7 @@ const FAMILY = {
|
|
|
109
109
|
'review-plan': { artifact: 'review-plan', started: ['technical-design', 'technical-design-review'], completed: ['technical-design', 'technical-design-review'], target: 'technical-design-review', label: 'Review Plan' },
|
|
110
110
|
code: { artifact: 'code', started: ['technical-design-review', 'code-review'], completed: ['technical-design-review', 'code-review'], target: 'code', label: 'Code Task' },
|
|
111
111
|
'review-code': { artifact: 'review-code', started: ['code', 'code-review', 'commit'], completed: ['code', 'code-review', 'commit'], target: 'code-review', label: 'Review Code' },
|
|
112
|
-
'manual-validation': { artifact: 'manual-validation', started: ['code-review'], completed: ['code-review'], target: null, label: 'Complete Manual Validation' }
|
|
112
|
+
'manual-validation': { artifact: 'manual-validation', started: ['code-review', 'commit'], completed: ['code-review', 'commit'], target: null, label: 'Complete Manual Validation' }
|
|
113
113
|
} as const;
|
|
114
114
|
type EventFamily = keyof typeof FAMILY;
|
|
115
115
|
|
package/lib/task/issue-form.ts
CHANGED
|
@@ -4,6 +4,8 @@ type TaskFields = {
|
|
|
4
4
|
title: string;
|
|
5
5
|
description: string;
|
|
6
6
|
requirements: string;
|
|
7
|
+
taskInput: string;
|
|
8
|
+
taskInputHeading: string;
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
// Field id -> task value mapping (single source of truth, per HD-1).
|
|
@@ -84,6 +86,8 @@ function renderTemplateBody(formText: string, fields: TaskFields): string {
|
|
|
84
86
|
const value = mapped.trim() === '' ? PLACEHOLDER : mapped;
|
|
85
87
|
sections.push(`### ${label}\n\n${value}`);
|
|
86
88
|
}
|
|
89
|
+
const taskInput = fields.taskInput.trim() === '' ? PLACEHOLDER : fields.taskInput;
|
|
90
|
+
sections.push(`### ${fields.taskInputHeading}\n\n${taskInput}`);
|
|
87
91
|
return `${sections.join('\n\n')}\n`;
|
|
88
92
|
}
|
|
89
93
|
|
|
@@ -12,7 +12,16 @@ export const DEFAULT_POST_REVIEW_EXCLUDES: string[] = [];
|
|
|
12
12
|
// The leading ":/" makes coverage fail-closed (everything from the repo root);
|
|
13
13
|
// each exclude becomes a top-level negative pathspec. Projects extend the
|
|
14
14
|
// denylist via review.post_review_exclude_globs (union, deduped).
|
|
15
|
-
type ReviewGlobConfig = { post_review_exclude_globs?: unknown };
|
|
15
|
+
type ReviewGlobConfig = Record<string, unknown> & { post_review_exclude_globs?: unknown };
|
|
16
|
+
|
|
17
|
+
export function loadPostReviewConfig(repoRoot: string): ReviewGlobConfig {
|
|
18
|
+
try {
|
|
19
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, ".agents", ".airc.json"), "utf8"));
|
|
20
|
+
return config.review && typeof config.review === "object" ? config.review : {};
|
|
21
|
+
} catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
16
25
|
|
|
17
26
|
export function resolvePostReviewGlobs(
|
|
18
27
|
config: ReviewGlobConfig = {},
|
|
@@ -10,11 +10,14 @@ import {
|
|
|
10
10
|
extractReviewDiffFingerprint,
|
|
11
11
|
extractReviewedSnapshotTree,
|
|
12
12
|
findAuthoritativeReviewCodeArtifact,
|
|
13
|
+
loadPostReviewConfig,
|
|
13
14
|
parseReviewVerdict,
|
|
14
15
|
resolvePostReviewGlobs
|
|
15
16
|
} from "./verification-review.ts";
|
|
16
17
|
import { check as checkPlatformSync } from "../platform/verification-sync.ts";
|
|
17
18
|
import { check as checkRequiredChecks } from "../platform/verification-required.ts";
|
|
19
|
+
import { inspectPlatformPullRequest } from "../platform/pull-requests.ts";
|
|
20
|
+
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.ts";
|
|
18
21
|
import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
|
|
19
22
|
import { parseLedger } from "./ledger.ts";
|
|
20
23
|
import { loadVerificationConfig } from "./verification-config.ts";
|
|
@@ -268,20 +271,6 @@ function loadProjectName(): any {
|
|
|
268
271
|
}
|
|
269
272
|
}
|
|
270
273
|
|
|
271
|
-
function loadReviewConfig(): any {
|
|
272
|
-
const configPath = path.join(repoRoot, ".agents", ".airc.json");
|
|
273
|
-
if (!fs.existsSync(configPath)) {
|
|
274
|
-
return {};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
279
|
-
return config.review && typeof config.review === "object" ? config.review : {};
|
|
280
|
-
} catch {
|
|
281
|
-
return {};
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
274
|
function checkArtifact({ taskDir, config, artifactFile }: any): any {
|
|
286
275
|
const resolvedArtifact = resolveArtifactPath(taskDir, config.file_pattern, artifactFile);
|
|
287
276
|
if (!resolvedArtifact.ok) {
|
|
@@ -598,7 +587,7 @@ function resolveReviewSetting(config: any, key: any, fallback: any): any {
|
|
|
598
587
|
if (config && config[key] !== undefined && config[key] !== null) {
|
|
599
588
|
return config[key];
|
|
600
589
|
}
|
|
601
|
-
const reviewConfig =
|
|
590
|
+
const reviewConfig = loadPostReviewConfig(repoRoot);
|
|
602
591
|
if (reviewConfig[key] !== undefined && reviewConfig[key] !== null) {
|
|
603
592
|
return reviewConfig[key];
|
|
604
593
|
}
|
|
@@ -706,7 +695,7 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
|
706
695
|
}
|
|
707
696
|
|
|
708
697
|
const sha = baselineSource.sha;
|
|
709
|
-
const globs = resolvePostReviewGlobs(config,
|
|
698
|
+
const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
|
|
710
699
|
let commits;
|
|
711
700
|
try {
|
|
712
701
|
const out = execFileSync("git", ["-C", gitRoot, "rev-list", `${sha}..HEAD`, "--", ...globs], { encoding: "utf8" });
|
|
@@ -719,6 +708,33 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
|
719
708
|
return passResult("post-review-commit", `No post-review commits to code/rule paths since ${sha.slice(0, 8)}`);
|
|
720
709
|
}
|
|
721
710
|
|
|
711
|
+
if (task.ok && Number(task.metadata.pr_number) > 0) {
|
|
712
|
+
const inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
|
|
713
|
+
if (inspected.pullRequest) {
|
|
714
|
+
let localHead = "";
|
|
715
|
+
try {
|
|
716
|
+
localHead = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
717
|
+
} catch {
|
|
718
|
+
return blockedResult("post-review-commit", "Unable to resolve the local HEAD");
|
|
719
|
+
}
|
|
720
|
+
const relation = resolveReviewedHeadRelation({
|
|
721
|
+
gitRoot,
|
|
722
|
+
localHead,
|
|
723
|
+
lastReviewedCommit: sha,
|
|
724
|
+
pullRequest: inspected.pullRequest,
|
|
725
|
+
pathspecs: globs
|
|
726
|
+
});
|
|
727
|
+
if (relation.status === "merged-equivalent") {
|
|
728
|
+
return passResult("post-review-commit", `Reviewed PR head is content-equivalent to squash merge ${relation.mergeCommit.slice(0, 8)}`);
|
|
729
|
+
}
|
|
730
|
+
if (relation.status === "blocked") {
|
|
731
|
+
return blockedResult("post-review-commit", relation.message);
|
|
732
|
+
}
|
|
733
|
+
} else if (inspected.status === "blocked") {
|
|
734
|
+
return blockedResult("post-review-commit", inspected.error?.message || "PR merge snapshot is unavailable");
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
722
738
|
let exempt = false;
|
|
723
739
|
try {
|
|
724
740
|
exempt = task.ok && parseLedger(task.content).some(
|
|
@@ -788,7 +804,7 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
|
|
|
788
804
|
|
|
789
805
|
let actualSnapshot;
|
|
790
806
|
try {
|
|
791
|
-
actualSnapshot = snapshotReview({ cwd: gitRoot, mode: "worktree", baseline, globs: resolvePostReviewGlobs({},
|
|
807
|
+
actualSnapshot = snapshotReview({ cwd: gitRoot, mode: "worktree", baseline, globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repoRoot)) });
|
|
792
808
|
} catch {
|
|
793
809
|
return blockedResult(
|
|
794
810
|
"review-fact",
|
package/lib/task/verification.ts
CHANGED
|
@@ -63,7 +63,7 @@ const VERIFICATION_CATALOG: Readonly<Record<VerificationEvent, VerificationSpec>
|
|
|
63
63
|
'block-task.completed': gate('block-task', 'blocked'),
|
|
64
64
|
'cancel-task.completed': gate('cancel-task', 'completed'),
|
|
65
65
|
'commit.completed': gate('commit', 'active'),
|
|
66
|
-
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['review-ledger', 'post-review-commit', '
|
|
66
|
+
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['review-ledger', 'post-review-commit', 'platform-sync-preflight'] },
|
|
67
67
|
'complete-task.completed': gate('complete-task', 'completed'),
|
|
68
68
|
'create-pr.completed': gate('create-pr', 'active'),
|
|
69
69
|
'create-task.completed': gate('create-task', 'active'),
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Release Commands
|
|
2
2
|
|
|
3
|
-
This code platform does not provide
|
|
3
|
+
This code platform does not provide a release-note adapter.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use `agent-infra-internal platform-release-notes context` to receive a structured `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op. Do not probe another platform client. Publishing remains a no-op until an adapter is provided.
|
|
@@ -1,52 +1,22 @@
|
|
|
1
1
|
# Release Platform Commands
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Release-note platform operations use a typed internal intent. Callers consume structured JSON and do not interpret platform commands, raw fields, identity email rules, or authentication errors.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Collect Release-note Context
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
agent-infra-internal platform-release-notes context \
|
|
9
|
+
--from-tag "v{prev-version}" --to-tag "v{version}" \
|
|
10
|
+
--branch "{branch}" --history-limit 3
|
|
10
11
|
```
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
The result contains release history, pull requests, closing Issues, and normalized contributor identities. Unsupported platforms return a stable `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
When needed, read the linked Issue:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
gh issue view {issue-number} --json number,title,labels,url
|
|
22
|
-
```
|
|
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
|
-
|
|
40
|
-
## Publish the Release Notes
|
|
41
|
-
|
|
42
|
-
The GitHub Release for `v{version}` is created and published automatically by the release workflow so Homebrew bottles have a stable upload target. This command writes the curated notes onto that existing Release, falling back to creating it if it does not exist yet.
|
|
15
|
+
## Publish Release Notes
|
|
43
16
|
|
|
44
17
|
```bash
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else
|
|
48
|
-
gh release create "v{version}" --title "v{version}" --notes-file "{notes-file}"
|
|
49
|
-
fi
|
|
18
|
+
agent-infra-internal platform-release-notes publish \
|
|
19
|
+
--tag "v{version}" --title "v{version}" --notes-file "{notes-file}"
|
|
50
20
|
```
|
|
51
21
|
|
|
52
|
-
|
|
22
|
+
The command updates an existing Release or creates a missing one. `--dry-run` only plans the operation. Exit codes `0/1/2` mean success, failure, and blocked.
|
|
@@ -1,52 +1,22 @@
|
|
|
1
1
|
# Release 平台命令
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
发布说明平台操作统一使用 typed internal intent。调用方只解释结构化 JSON,不解析平台命令、原始字段、身份邮箱规则或认证错误。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 收集发布说明上下文
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
agent-infra-internal platform-release-notes context \
|
|
9
|
+
--from-tag "v{prev-version}" --to-tag "v{version}" \
|
|
10
|
+
--branch "{branch}" --history-limit 3
|
|
10
11
|
```
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
gh pr list --state merged --base "{branch}" --json number,title,mergedAt,labels
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
必要时读取关联 Issue:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
gh issue view {issue-number} --json number,title,labels,url
|
|
22
|
-
```
|
|
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`。
|
|
13
|
+
结果包含历史发布、PR、关联 Issue 和规范化贡献者身份。不支持的平台返回稳定的 `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op。
|
|
39
14
|
|
|
40
15
|
## 发布 Release notes
|
|
41
16
|
|
|
42
|
-
`v{version}` 的 GitHub Release 由 release 工作流自动创建并发布,为 Homebrew bottle 提供稳定的上传落点。本命令把精修后的 notes 写到这个已存在的 Release 上;若 Release 尚不存在则兜底创建。
|
|
43
|
-
|
|
44
17
|
```bash
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else
|
|
48
|
-
gh release create "v{version}" --title "v{version}" --notes-file "{notes-file}"
|
|
49
|
-
fi
|
|
18
|
+
agent-infra-internal platform-release-notes publish \
|
|
19
|
+
--tag "v{version}" --title "v{version}" --notes-file "{notes-file}"
|
|
50
20
|
```
|
|
51
21
|
|
|
52
|
-
|
|
22
|
+
命令更新既有 Release,缺失时创建;`--dry-run` 只规划操作。退出码 `0/1/2` 分别表示成功、失败和阻塞。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Release 命令
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
当前代码平台未提供发布说明 adapter。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
调用 `agent-infra-internal platform-release-notes context` 会返回结构化的 `PLATFORM_RELEASE_NOTES_UNSUPPORTED` no-op,不得探测其他平台 client。在提供 adapter 前,发布操作同样保持 no-op。
|
|
@@ -92,7 +92,8 @@ When an executor judges an item to be a key design decision that needs human rul
|
|
|
92
92
|
- `commit` reads only the highest-round Approved `review-code` artifact. Before committing it requires `pre_head == R`, complete worktree tree `W == T`, and normalized staged tree `S == T`; any mismatch blocks before `git commit` and reports added, missing, and different paths for both comparisons.
|
|
93
93
|
- After a successful commit it sets `B=last_reviewed_commit=<new_head>`; B means only a reviewed snapshot anchored to a Git commit.
|
|
94
94
|
- The `complete-task` `post-review-commit` gate uses only B. When B is absent, malformed, or missing as a Git object, it reports `reviewed snapshot was not anchored` and never falls back to R.
|
|
95
|
-
- If new commits touch code / rule paths after B, the gate blocks and requires a fresh `review-code`.
|
|
95
|
+
- If new commits touch code / rule paths after B, the gate blocks and requires a fresh `review-code`. The only automatic exception is a bound merged GitHub PR whose platform snapshot proves B is the original PR head, whose merge commit is in local HEAD history, whose reviewed changes exactly match the normalized patch of a single-parent squash commit, and whose protected paths have no later commits. Missing platform or Git evidence fails closed.
|
|
96
|
+
- Required checks remain a pre-merge responsibility of branch protection / rulesets and the `review-code` / `watch-pr` routes. The post-review-commit gate still applies the strict / squash-equivalent relation above independently.
|
|
96
97
|
- **Exemption**: append a ledger row `| PRC-1 | post-review-commit | - | - | human-decided | <ruling note> |` recording that a human explicitly allowed those commits without re-review.
|
|
97
98
|
|
|
98
99
|
## Gate behavior cheat sheet
|
|
@@ -92,7 +92,8 @@
|
|
|
92
92
|
- `commit` 只读取最高轮 Approved `review-code` 产物;提交前比较 `pre_head == R`、完整工作区树 `W == T`、规范化暂存树 `S == T`,任一失配都在 `git commit` 前阻断并报告两组 added/missing/different 路径。
|
|
93
93
|
- 成功提交后令 `B=last_reviewed_commit=<new_head>`;`B` 只表示已经落到 Git commit 的审查锚点。
|
|
94
94
|
- `complete-task` 的 `post-review-commit` gate 只使用 B;B 缺失、畸形或对象不存在时报告 `reviewed snapshot was not anchored`,不得回退 R。
|
|
95
|
-
- 若 B 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code
|
|
95
|
+
- 若 B 之后代码 / 规则路径出现新提交,gate 会拦截,要求重新 `review-code`。唯一自动例外是绑定 GitHub PR 已合并,且平台快照证明 B 是原 PR head、merge commit 位于本地 HEAD 历史中、受审查变更与单父 squash commit 的规范化补丁完全一致,并且 merge 后受保护路径没有新提交;缺少任一平台或 Git 证据时 fail closed。
|
|
96
|
+
- required checks 由合并前的分支保护 / ruleset 与 `review-code` / `watch-pr` 路由负责。post-review-commit 仍独立执行上述严格 / squash 等价判定。
|
|
96
97
|
- **豁免**:在账本追加一行 `| PRC-1 | post-review-commit | - | - | human-decided | <裁定说明> |`,记录人工明确允许该批提交免复审。
|
|
97
98
|
|
|
98
99
|
## gate 行为速查
|
|
@@ -41,6 +41,24 @@ This mirrors "Goal-Driven Execution" in AGENTS.md: define a verifiable success c
|
|
|
41
41
|
|
|
42
42
|
## Project-specific test policy
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
Complete each item below for this repository. Record the chosen behavior and its boundaries so contributors can select and validate tests consistently.
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
### 1. Test commands
|
|
47
|
+
|
|
48
|
+
> TODO: Document the complete test command and any faster commands used during development.
|
|
49
|
+
|
|
50
|
+
### 2. Test directories or tiers
|
|
51
|
+
|
|
52
|
+
> TODO: Document the test directories or tiers and the rule for choosing among them.
|
|
53
|
+
|
|
54
|
+
### 3. Coverage
|
|
55
|
+
|
|
56
|
+
> TODO: Document how coverage is collected, any thresholds, and whether coverage blocks changes.
|
|
57
|
+
|
|
58
|
+
### 4. Continuous integration
|
|
59
|
+
|
|
60
|
+
> TODO: Document which CI checks run tests and which checks are blocking or informational.
|
|
61
|
+
|
|
62
|
+
### 5. Reporting services
|
|
63
|
+
|
|
64
|
+
> TODO: Document any external reporting services, badges, or artifacts and whether they affect acceptance.
|
|
@@ -41,6 +41,24 @@ assert.match(content, /^name: code-task$/m); // 正向断言已足够
|
|
|
41
41
|
|
|
42
42
|
## 项目级测试策略
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
请为本仓库填写以下各项,记录选定行为及其边界,以便贡献者一致地选择并验证测试。
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
### 1. 测试命令
|
|
47
|
+
|
|
48
|
+
> TODO: 记录完整测试命令,以及开发期间使用的可选快速命令。
|
|
49
|
+
|
|
50
|
+
### 2. 测试目录或层级
|
|
51
|
+
|
|
52
|
+
> TODO: 记录测试目录或层级,以及选择对应目录或层级的规则。
|
|
53
|
+
|
|
54
|
+
### 3. 覆盖率
|
|
55
|
+
|
|
56
|
+
> TODO: 记录覆盖率采集方式、阈值,以及覆盖率是否阻塞变更。
|
|
57
|
+
|
|
58
|
+
### 4. 持续集成
|
|
59
|
+
|
|
60
|
+
> TODO: 记录运行测试的 CI 检查,以及哪些检查属于阻塞项或信息项。
|
|
61
|
+
|
|
62
|
+
### 5. 报告服务
|
|
63
|
+
|
|
64
|
+
> TODO: 记录外部报告服务、徽章或构件,以及它们是否影响验收。
|
|
@@ -11,7 +11,7 @@ description: >
|
|
|
11
11
|
## Boundary / Critical Rules
|
|
12
12
|
|
|
13
13
|
- This skill only outputs a requirements analysis document (`analysis.md` or `analysis-r{N}.md`) and does not modify any business code
|
|
14
|
-
- Base the analysis strictly on the existing requirements, context, and source information in `task.md`
|
|
14
|
+
- Base the analysis strictly on the existing task input, requirements, context, and source information in `task.md`
|
|
15
15
|
- After executing this skill, you **must** immediately update task status in task.md
|
|
16
16
|
|
|
17
17
|
Version stamp rule: when creating or updating `task.md` frontmatter, read `.agents/rules/version-stamp.md` first and write or refresh `agent_infra_version`.
|
|
@@ -56,6 +56,7 @@ Run `agent-infra-internal task-artifact {task-id} inspect --family analysis`. Co
|
|
|
56
56
|
### 3. Read Task Context
|
|
57
57
|
|
|
58
58
|
Read `task.md` carefully to understand:
|
|
59
|
+
- sources, facts and evidence, constraints, decision states, acceptance criteria, and open questions captured under `## Task Input` (fall back compatibly when the section is absent)
|
|
59
60
|
- task title, description, and requirement list
|
|
60
61
|
- context information (Issue, PR, branch, alert numbers, etc.)
|
|
61
62
|
- currently known affected files and constraints
|
|
@@ -89,6 +90,8 @@ Runs after Step 0 state check and Step 3 (questioning is an external-state actio
|
|
|
89
90
|
- no answer carried → restate `pending_question` and take Scenario B early-exit below (do not increase `question_count`).
|
|
90
91
|
|
|
91
92
|
**4.3 Sufficiency judgment** (objective checklist; any gap hit means insufficient):
|
|
93
|
+
- first aggregate `## Task Input`, description, context, requirements, and remote sources; an analysis-owned `## Requirements` section being unpopulated is not by itself insufficient;
|
|
94
|
+
- goals, scope, constraints, or acceptance criteria already recorded in task input count as provided and must not be asked again;
|
|
92
95
|
- description/requirements empty, or a single sentence with no verifiable acceptance criteria;
|
|
93
96
|
- missing goal or impact scope (unclear what to change / who is affected);
|
|
94
97
|
- requirement items contradict each other, or key terms are undefined and block analysis.
|