@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
|
@@ -3,6 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { inspectRequiredChecks } from "./pr-checks.js";
|
|
5
5
|
import { resolvePlatformContext } from "./context.js";
|
|
6
|
+
import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.js";
|
|
7
|
+
import { loadPostReviewConfig, resolvePostReviewGlobs } from "../task/review-fingerprint.js";
|
|
6
8
|
const CHECK_TYPE = "required-checks";
|
|
7
9
|
const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
8
10
|
function validPrNumber(value) {
|
|
@@ -44,13 +46,28 @@ export function evaluateRequiredChecks(context, shared) {
|
|
|
44
46
|
if (inspection.status === "failed") {
|
|
45
47
|
return shared.failResult(CHECK_TYPE, inspection.error?.message || "Required checks are unavailable", "check_failed");
|
|
46
48
|
}
|
|
47
|
-
const prHead = inspection.pullRequest?.headSha;
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const prHead = inspection.pullRequest?.head?.sha || inspection.pullRequest?.headSha;
|
|
50
|
+
const relation = context.relation || (context.gitRoot && inspection.pullRequest?.head
|
|
51
|
+
? resolveReviewedHeadRelation({
|
|
52
|
+
gitRoot: context.gitRoot,
|
|
53
|
+
localHead,
|
|
54
|
+
lastReviewedCommit: reviewedHead,
|
|
55
|
+
pullRequest: inspection.pullRequest,
|
|
56
|
+
pathspecs: context.pathspecs || [":/"]
|
|
57
|
+
})
|
|
58
|
+
: localHead === reviewedHead && localHead === prHead
|
|
59
|
+
? { status: "strict" }
|
|
60
|
+
: { status: "failed" });
|
|
61
|
+
if (relation.status !== "strict" && relation.status !== "merged-equivalent") {
|
|
62
|
+
const message = relation.message || "Local HEAD, last reviewed commit, and PR head must match";
|
|
63
|
+
return relation.status === "blocked"
|
|
64
|
+
? shared.blockedResult(CHECK_TYPE, message, "dependency_error")
|
|
65
|
+
: shared.failResult(CHECK_TYPE, message, "check_failed");
|
|
50
66
|
}
|
|
51
67
|
const state = inspection.checks?.state;
|
|
52
68
|
if (state === "passed" || state === "no-required") {
|
|
53
|
-
|
|
69
|
+
const suffix = relation.status === "merged-equivalent" ? " via verified squash merge equivalence" : "";
|
|
70
|
+
return shared.passResult(CHECK_TYPE, `Required checks are ${state} for PR head ${prHead}${suffix}`);
|
|
54
71
|
}
|
|
55
72
|
if (state === "pending") {
|
|
56
73
|
return shared.blockedResult(CHECK_TYPE, inspection.error?.message || "Required checks are pending", "check_pending");
|
|
@@ -73,6 +90,13 @@ export function check({ taskDir }, shared) {
|
|
|
73
90
|
if (!localHead)
|
|
74
91
|
return evaluateRequiredChecks({ metadata: task.metadata, localHead, inspection: null, prFlow }, shared);
|
|
75
92
|
const inspection = inspectRequiredChecks(task.metadata.id, { cwd: shared.repoRoot });
|
|
76
|
-
return evaluateRequiredChecks({
|
|
93
|
+
return evaluateRequiredChecks({
|
|
94
|
+
metadata: task.metadata,
|
|
95
|
+
localHead,
|
|
96
|
+
inspection,
|
|
97
|
+
prFlow,
|
|
98
|
+
gitRoot: shared.repoRoot,
|
|
99
|
+
pathspecs: resolvePostReviewGlobs({}, loadPostReviewConfig(shared.repoRoot))
|
|
100
|
+
}, shared);
|
|
77
101
|
}
|
|
78
102
|
//# sourceMappingURL=verification-required.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
import { engineDisplayName } from './engine.js';
|
|
3
|
+
import { runSafeEngine } from './shell.js';
|
|
4
|
+
export const BUILD_PROXY_ENV_KEYS = ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY'];
|
|
5
|
+
export const MIN_BUILD_PROXY_DOCKER_VERSION = '20.10.0';
|
|
6
|
+
export const MIN_BUILD_PROXY_BUILDKIT_VERSION = '0.9.0';
|
|
7
|
+
export function prepareBuildProxy(enabled, hostEnv, engine) {
|
|
8
|
+
const env = { ...hostEnv };
|
|
9
|
+
if (!enabled) {
|
|
10
|
+
return { args: [], env, redactionValues: [] };
|
|
11
|
+
}
|
|
12
|
+
const entries = BUILD_PROXY_ENV_KEYS.flatMap((key) => {
|
|
13
|
+
const value = hostEnv[key]?.trim();
|
|
14
|
+
return value ? [[key, value]] : [];
|
|
15
|
+
});
|
|
16
|
+
if (entries.length === 0) {
|
|
17
|
+
throw new Error(`No non-empty build proxy variables are set. Configure one of: ${BUILD_PROXY_ENV_KEYS.join(', ')}.`);
|
|
18
|
+
}
|
|
19
|
+
for (const [key, value] of entries)
|
|
20
|
+
env[key] = value;
|
|
21
|
+
if (engine === 'wsl2') {
|
|
22
|
+
const existing = (env.WSLENV ?? '').split(':').filter(Boolean);
|
|
23
|
+
env.WSLENV = [...new Set([...existing, ...entries.map(([key]) => key)])].join(':');
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
args: entries.flatMap(([key]) => ['--secret', `id=${key},env=${key}`]),
|
|
27
|
+
env,
|
|
28
|
+
redactionValues: entries.map(([, value]) => value)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function parsedVersion(raw) {
|
|
32
|
+
return semver.coerce(raw.trim())?.version ?? null;
|
|
33
|
+
}
|
|
34
|
+
export function assertBuildProxyCompatibility(engine, runSafeEngineFn = runSafeEngine) {
|
|
35
|
+
const dockerRaw = runSafeEngineFn(engine, 'docker', ['version', '--format', '{{.Server.Version}}']);
|
|
36
|
+
const dockerVersion = parsedVersion(dockerRaw);
|
|
37
|
+
if (!dockerVersion || !semver.gte(dockerVersion, MIN_BUILD_PROXY_DOCKER_VERSION)) {
|
|
38
|
+
throw new Error(`Build proxy requires Docker Engine ${MIN_BUILD_PROXY_DOCKER_VERSION} or newer; `
|
|
39
|
+
+ `observed ${dockerVersion ?? 'an unparseable version'}.`);
|
|
40
|
+
}
|
|
41
|
+
const buildxRaw = runSafeEngineFn(engine, 'docker', ['buildx', 'inspect', '--bootstrap']);
|
|
42
|
+
const versions = [...buildxRaw.matchAll(/BuildKit(?:\s+version)?:\s*v?(\d+\.\d+\.\d+(?:[-+][^\s]+)?)/gi)]
|
|
43
|
+
.map((match) => parsedVersion(match[1] ?? ''))
|
|
44
|
+
.filter((version) => version !== null);
|
|
45
|
+
if (versions.length === 0) {
|
|
46
|
+
throw new Error(`Build proxy requires BuildKit ${MIN_BUILD_PROXY_BUILDKIT_VERSION} or newer, `
|
|
47
|
+
+ 'but the active builder version could not be determined.');
|
|
48
|
+
}
|
|
49
|
+
const oldVersion = versions.find((version) => !semver.gte(version, MIN_BUILD_PROXY_BUILDKIT_VERSION));
|
|
50
|
+
if (oldVersion) {
|
|
51
|
+
throw new Error(`Build proxy observed BuildKit ${oldVersion}; version ${MIN_BUILD_PROXY_BUILDKIT_VERSION} or newer is required.`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function redactBuildProxyValues(text, values) {
|
|
55
|
+
return [...new Set(values.filter(Boolean))]
|
|
56
|
+
.sort((left, right) => right.length - left.length)
|
|
57
|
+
.reduce((result, value) => result.replaceAll(value, '[REDACTED_BUILD_PROXY]'), text);
|
|
58
|
+
}
|
|
59
|
+
export function buildProxyFailureHint(engine) {
|
|
60
|
+
return `Build-step proxy inheritance is enabled for ${engineDisplayName(engine)}. `
|
|
61
|
+
+ 'Image pulls and Dockerfile FROM resolution use the Docker daemon or builder proxy configuration.';
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=build-proxy.js.map
|
|
@@ -10,6 +10,7 @@ import * as toml from 'smol-toml';
|
|
|
10
10
|
import { loadConfig } from '../config.js';
|
|
11
11
|
import { assertValidBranchName, containerName, containerNameCandidates, parsePositiveIntegerOption, sandboxBranchLabel, sandboxImageConfigLabel, sandboxImageRefreshLabel, sandboxLabel, shareBranchDir, shareCommonDir, shellConfigDir, worktreeDirCandidates } from '../constants.js';
|
|
12
12
|
import { prepareDockerfile } from '../dockerfile.js';
|
|
13
|
+
import { assertBuildProxyCompatibility, buildProxyFailureHint, prepareBuildProxy, redactBuildProxyValues } from '../build-proxy.js';
|
|
13
14
|
import { detectEngine, ensureDocker } from '../engine.js';
|
|
14
15
|
import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from '../shell.js';
|
|
15
16
|
import { resolveTaskBranch } from '../task-resolver.js';
|
|
@@ -49,7 +50,7 @@ alias gy='gemini --yolo; tput ed'
|
|
|
49
50
|
`;
|
|
50
51
|
const CONTAINER_HOME = '/home/devuser';
|
|
51
52
|
const CONTAINER_SHELL_CONFIG_MOUNT = `${CONTAINER_HOME}/.host-shell-config`;
|
|
52
|
-
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P]
|
|
53
|
+
const USAGE = `Usage: ai sandbox create <branch> [base] [--cpu <n>] [--memory <n>] [--no-refresh] [--inherit-proxy|-P] [--inherit-build-proxy|-B]
|
|
53
54
|
|
|
54
55
|
Host aliases:
|
|
55
56
|
${'~'}/.agent-infra/aliases/sandbox.sh is auto-created on first run and exposed
|
|
@@ -59,7 +60,9 @@ Host aliases:
|
|
|
59
60
|
|
|
60
61
|
Proxy:
|
|
61
62
|
--inherit-proxy, -P Copy non-empty standard host proxy variables into the
|
|
62
|
-
container environment through the private env file
|
|
63
|
+
container environment through the private env file.
|
|
64
|
+
--inherit-build-proxy, -B Pass uppercase HTTP_PROXY, HTTPS_PROXY, and NO_PROXY
|
|
65
|
+
to managed image build steps for this invocation.`;
|
|
63
66
|
const HOST_PROXY_ENV_KEYS = [
|
|
64
67
|
'http_proxy',
|
|
65
68
|
'HTTP_PROXY',
|
|
@@ -70,6 +73,15 @@ const HOST_PROXY_ENV_KEYS = [
|
|
|
70
73
|
'no_proxy',
|
|
71
74
|
'NO_PROXY'
|
|
72
75
|
];
|
|
76
|
+
const MANAGED_GITHUB_GIT_CONFIG = [
|
|
77
|
+
'[credential]',
|
|
78
|
+
'\thelper = !gh auth git-credential',
|
|
79
|
+
'[credential "https://github.com"]',
|
|
80
|
+
'\thelper =',
|
|
81
|
+
'\thelper = !gh auth git-credential',
|
|
82
|
+
'[url "https://github.com/"]',
|
|
83
|
+
'\tinsteadOf = git@github.com:'
|
|
84
|
+
];
|
|
73
85
|
function resolveToolDirs(config, tools, branch) {
|
|
74
86
|
return tools.map((tool) => {
|
|
75
87
|
const candidates = toolConfigDirCandidates(tool, config.project, branch);
|
|
@@ -158,6 +170,14 @@ function normalizeContainerHomeSeparators(content) {
|
|
|
158
170
|
const containerHomePattern = new RegExp(`${escapeRegExp(CONTAINER_HOME)}\\S*`, 'g');
|
|
159
171
|
return content.replace(containerHomePattern, (value) => value.replaceAll('\\', '/'));
|
|
160
172
|
}
|
|
173
|
+
function isGitHubCliCredentialHelper(line) {
|
|
174
|
+
const helperMatch = line.match(/^\s*helper\s*=\s*(.+?)\s*$/i);
|
|
175
|
+
if (!helperMatch) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const helper = (helperMatch[1] ?? '').trim().replace(/^"(.*)"$/, '$1');
|
|
179
|
+
return /^!\s*(?:"[^"]*[\\/]+gh"|(?:\S*[\\/]+)?gh)\s+auth\s+git-credential(?:\s|$)/i.test(helper);
|
|
180
|
+
}
|
|
161
181
|
export function sanitizeGitConfig(gitconfig, home, { stripGpg = false, repoRoot = '' } = {}) {
|
|
162
182
|
const posixHome = home.replaceAll('\\', '/');
|
|
163
183
|
const normalizedGitconfig = gitconfig
|
|
@@ -170,10 +190,12 @@ export function sanitizeGitConfig(gitconfig, home, { stripGpg = false, repoRoot
|
|
|
170
190
|
const sanitized = [];
|
|
171
191
|
let inGpgSection = false;
|
|
172
192
|
let currentSection = '';
|
|
193
|
+
let currentSectionName = '';
|
|
173
194
|
for (const line of lines) {
|
|
174
195
|
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
175
196
|
if (sectionMatch) {
|
|
176
197
|
const sectionName = (sectionMatch[1] ?? '').trim();
|
|
198
|
+
currentSectionName = sectionName;
|
|
177
199
|
currentSection = ((sectionName.match(/^([^\s"]+)/)?.[1]) ?? '').toLowerCase();
|
|
178
200
|
inGpgSection = /^gpg(?:\s+"[^"]+")?$/i.test(sectionName);
|
|
179
201
|
if (stripGpg && inGpgSection) {
|
|
@@ -199,9 +221,19 @@ export function sanitizeGitConfig(gitconfig, home, { stripGpg = false, repoRoot
|
|
|
199
221
|
if (stripGpg && currentSection === 'user' && /^\s*signingKey\s*=.*$/i.test(line)) {
|
|
200
222
|
continue;
|
|
201
223
|
}
|
|
224
|
+
if (currentSection === 'credential' && isGitHubCliCredentialHelper(line)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (/^url\s+"https:\/\/github\.com\/"$/i.test(currentSectionName)
|
|
228
|
+
&& /^\s*insteadOf\s*=\s*git@github\.com:\s*$/i.test(line)) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
202
231
|
sanitized.push(line);
|
|
203
232
|
}
|
|
204
|
-
return
|
|
233
|
+
return [
|
|
234
|
+
...appendSafeDirectories(sanitized, repoRoot),
|
|
235
|
+
...MANAGED_GITHUB_GIT_CONFIG
|
|
236
|
+
].join('\n');
|
|
205
237
|
}
|
|
206
238
|
export function hostHasGpgKeys(home, execFn = execFileSync) {
|
|
207
239
|
return currentKeyringFingerprint(home, execFn) !== null;
|
|
@@ -928,7 +960,7 @@ export function buildTmpfsRunArgs(containerMount, tmpfs) {
|
|
|
928
960
|
const size = tmpfs.size ?? '512m';
|
|
929
961
|
return ['--tmpfs', `${containerMount}:rw,size=${size}`];
|
|
930
962
|
}
|
|
931
|
-
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env, refresh = false, lastRefresh } = {}) {
|
|
963
|
+
export function buildImage(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, runVerboseFn = runVerboseEngine, env = process.env, refresh = false, lastRefresh, buildProxyArgs = [], buildEnv } = {}) {
|
|
932
964
|
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
933
965
|
runVerboseFn(selectedEngine, 'docker', buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
934
966
|
engine: selectedEngine,
|
|
@@ -936,8 +968,9 @@ export function buildImage(config, tools, dockerfilePath, imageSignature, { engi
|
|
|
936
968
|
runSafeFn,
|
|
937
969
|
env,
|
|
938
970
|
refresh,
|
|
939
|
-
lastRefresh
|
|
940
|
-
|
|
971
|
+
lastRefresh,
|
|
972
|
+
buildProxyArgs
|
|
973
|
+
}), { cwd: config.repoRoot, env: buildEnv });
|
|
941
974
|
}
|
|
942
975
|
function readImageLabels(config, engine) {
|
|
943
976
|
return parseImageLabels(runSafeEngine(engine, 'docker', [
|
|
@@ -958,6 +991,7 @@ export async function create(args) {
|
|
|
958
991
|
memory: { type: 'string' },
|
|
959
992
|
'no-refresh': { type: 'boolean' },
|
|
960
993
|
'inherit-proxy': { type: 'boolean', short: 'P' },
|
|
994
|
+
'inherit-build-proxy': { type: 'boolean', short: 'B' },
|
|
961
995
|
help: { type: 'boolean', short: 'h' }
|
|
962
996
|
}
|
|
963
997
|
});
|
|
@@ -1027,6 +1061,12 @@ export async function create(args) {
|
|
|
1027
1061
|
&& isRefreshDue(currentLastRefresh, now, effectiveConfig.refreshIntervalDays);
|
|
1028
1062
|
const needsImageBuild = signatureStale || refreshDue;
|
|
1029
1063
|
if (needsImageBuild) {
|
|
1064
|
+
if (values['inherit-build-proxy'] && effectiveConfig.dockerfile) {
|
|
1065
|
+
throw new Error('Build proxy inheritance is unavailable with a custom sandbox Dockerfile.');
|
|
1066
|
+
}
|
|
1067
|
+
const buildProxy = prepareBuildProxy(values['inherit-build-proxy'] ?? false, process.env, engine);
|
|
1068
|
+
if (values['inherit-build-proxy'])
|
|
1069
|
+
assertBuildProxyCompatibility(engine);
|
|
1030
1070
|
const buildRefresh = !imageExists || refreshDue;
|
|
1031
1071
|
const buildLastRefresh = buildRefresh ? now : currentLastRefresh || 0;
|
|
1032
1072
|
p.log.step(refreshDue
|
|
@@ -1035,7 +1075,13 @@ export async function create(args) {
|
|
|
1035
1075
|
? 'Rebuilding stale image...'
|
|
1036
1076
|
: 'Building image for first use...');
|
|
1037
1077
|
try {
|
|
1038
|
-
buildImage(effectiveConfig, tools, preparedDockerfile.path, expectedImageSignature, {
|
|
1078
|
+
buildImage(effectiveConfig, tools, preparedDockerfile.path, expectedImageSignature, {
|
|
1079
|
+
engine,
|
|
1080
|
+
refresh: buildRefresh,
|
|
1081
|
+
lastRefresh: buildLastRefresh,
|
|
1082
|
+
buildProxyArgs: buildProxy.args,
|
|
1083
|
+
buildEnv: buildProxy.env
|
|
1084
|
+
});
|
|
1039
1085
|
p.log.success(refreshDue
|
|
1040
1086
|
? 'Image refreshed'
|
|
1041
1087
|
: imageExists
|
|
@@ -1045,10 +1091,12 @@ export async function create(args) {
|
|
|
1045
1091
|
catch (error) {
|
|
1046
1092
|
if (refreshDue && !signatureStale && imageExists) {
|
|
1047
1093
|
p.log.warn('Scheduled sandbox image refresh failed; continuing with the existing image. ' +
|
|
1048
|
-
commandErrorMessage(error))
|
|
1094
|
+
redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)
|
|
1095
|
+
+ (values['inherit-build-proxy'] ? ` ${buildProxyFailureHint(engine)}` : ''));
|
|
1049
1096
|
}
|
|
1050
1097
|
else {
|
|
1051
|
-
|
|
1098
|
+
const hint = values['inherit-build-proxy'] ? `\n${buildProxyFailureHint(engine)}` : '';
|
|
1099
|
+
throw new Error(`${redactBuildProxyValues(commandErrorMessage(error), buildProxy.redactionValues)}${hint}`);
|
|
1052
1100
|
}
|
|
1053
1101
|
}
|
|
1054
1102
|
}
|
|
@@ -9,15 +9,17 @@ import { runEngine, runOkEngine, runSafeEngine, runVerboseEngine } from '../shel
|
|
|
9
9
|
import { pruneSandboxDanglingImages } from '../image-prune.js';
|
|
10
10
|
import { resolveTools } from '../tools.js';
|
|
11
11
|
import { buildImageSignature, buildSandboxImageArgs, parseImageLabels, parseRefreshTimestamp } from '../image-build.js';
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
import { assertBuildProxyCompatibility, buildProxyFailureHint, prepareBuildProxy, redactBuildProxyValues } from '../build-proxy.js';
|
|
13
|
+
const USAGE = `Usage: ai sandbox rebuild [--quiet] [--refresh] [--inherit-build-proxy|-B]`;
|
|
14
|
+
export function buildArgs(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, env = process.env, refresh = false, lastRefresh, buildProxyArgs = [] } = {}) {
|
|
14
15
|
return buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, {
|
|
15
16
|
engine: engine ?? detectEngine(config),
|
|
16
17
|
runFn,
|
|
17
18
|
runSafeFn,
|
|
18
19
|
env,
|
|
19
20
|
refresh,
|
|
20
|
-
lastRefresh
|
|
21
|
+
lastRefresh,
|
|
22
|
+
buildProxyArgs
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
25
|
function readExistingLastRefresh(config, engine) {
|
|
@@ -41,6 +43,7 @@ export async function rebuild(args) {
|
|
|
41
43
|
options: {
|
|
42
44
|
refresh: { type: 'boolean' },
|
|
43
45
|
quiet: { type: 'boolean', short: 'q' },
|
|
46
|
+
'inherit-build-proxy': { type: 'boolean', short: 'B' },
|
|
44
47
|
help: { type: 'boolean', short: 'h' }
|
|
45
48
|
}
|
|
46
49
|
});
|
|
@@ -56,6 +59,12 @@ export async function rebuild(args) {
|
|
|
56
59
|
const refresh = values.refresh ?? false;
|
|
57
60
|
const engine = detectEngine(config);
|
|
58
61
|
await ensureDocker(config, undefined);
|
|
62
|
+
if (values['inherit-build-proxy'] && config.dockerfile) {
|
|
63
|
+
throw new Error('Build proxy inheritance is unavailable with a custom sandbox Dockerfile.');
|
|
64
|
+
}
|
|
65
|
+
const buildProxy = prepareBuildProxy(values['inherit-build-proxy'] ?? false, process.env, engine);
|
|
66
|
+
if (values['inherit-build-proxy'])
|
|
67
|
+
assertBuildProxyCompatibility(engine);
|
|
59
68
|
const lastRefresh = refresh ? Date.now() : readExistingLastRefresh(config, engine);
|
|
60
69
|
p.intro(pc.cyan('Rebuilding sandbox image'));
|
|
61
70
|
try {
|
|
@@ -65,9 +74,11 @@ export async function rebuild(args) {
|
|
|
65
74
|
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
66
75
|
engine,
|
|
67
76
|
refresh,
|
|
68
|
-
lastRefresh
|
|
77
|
+
lastRefresh,
|
|
78
|
+
buildProxyArgs: buildProxy.args
|
|
69
79
|
}), {
|
|
70
|
-
cwd: config.repoRoot
|
|
80
|
+
cwd: config.repoRoot,
|
|
81
|
+
env: buildProxy.env
|
|
71
82
|
});
|
|
72
83
|
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
73
84
|
}
|
|
@@ -76,12 +87,18 @@ export async function rebuild(args) {
|
|
|
76
87
|
runVerboseEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, {
|
|
77
88
|
engine,
|
|
78
89
|
refresh,
|
|
79
|
-
lastRefresh
|
|
80
|
-
|
|
90
|
+
lastRefresh,
|
|
91
|
+
buildProxyArgs: buildProxy.args
|
|
92
|
+
}), { cwd: config.repoRoot, env: buildProxy.env });
|
|
81
93
|
p.log.success(pc.green('Sandbox image rebuilt'));
|
|
82
94
|
}
|
|
83
95
|
pruneSandboxDanglingImages(config, engine);
|
|
84
96
|
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const message = redactBuildProxyValues(error instanceof Error ? error.message : String(error), buildProxy.redactionValues);
|
|
99
|
+
const hint = values['inherit-build-proxy'] ? `\n${buildProxyFailureHint(engine)}` : '';
|
|
100
|
+
throw new Error(`${message}${hint}`);
|
|
101
|
+
}
|
|
85
102
|
finally {
|
|
86
103
|
preparedDockerfile.cleanup();
|
|
87
104
|
}
|
|
@@ -14,7 +14,7 @@ export function buildImageSignature(preparedDockerfile, tools) {
|
|
|
14
14
|
.digest('hex')
|
|
15
15
|
.slice(0, 12);
|
|
16
16
|
}
|
|
17
|
-
export function buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, env = process.env, refresh = false, lastRefresh } = {}) {
|
|
17
|
+
export function buildSandboxImageArgs(config, tools, dockerfilePath, imageSignature, { engine, runFn = runEngine, runSafeFn = runSafeEngine, env = process.env, refresh = false, lastRefresh, buildProxyArgs = [] } = {}) {
|
|
18
18
|
const selectedEngine = engine ?? detectEngine({ engine: config.engine });
|
|
19
19
|
const { uid: hostUid, gid: hostGid } = resolveBuildUid({
|
|
20
20
|
engine: selectedEngine,
|
|
@@ -39,6 +39,7 @@ export function buildSandboxImageArgs(config, tools, dockerfilePath, imageSignat
|
|
|
39
39
|
'--label',
|
|
40
40
|
`${sandboxImageConfigLabel(config)}=${imageSignature}`
|
|
41
41
|
];
|
|
42
|
+
args.push(...buildProxyArgs);
|
|
42
43
|
if (lastRefresh !== undefined) {
|
|
43
44
|
args.push('--label', `${sandboxImageRefreshLabel(config)}=${lastRefresh}`);
|
|
44
45
|
}
|
|
@@ -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/*
|
|
@@ -10,11 +10,12 @@ Print a deterministic Issue body extracted from a task's task.md.
|
|
|
10
10
|
Omit <ref> Resolve the unique active task for the current branch.
|
|
11
11
|
<ref> Bare numeric short id, or a full TASK-YYYYMMDD-HHMMSS id.
|
|
12
12
|
--template <path> Render the final body for the given GitHub Issue Form (scenario A);
|
|
13
|
-
without it, print the default '描述 + 需求' body (scenario B).
|
|
13
|
+
without it, print the default '任务输入 + 描述 + 需求' body (scenario B).
|
|
14
14
|
|
|
15
|
-
Only the task title, '## 描述' and '## 需求' sections are ever
|
|
16
|
-
task.md
|
|
15
|
+
Only the task title, '## 任务输入', '## 描述' and '## 需求' sections are ever
|
|
16
|
+
emitted; the rest of task.md is never written to the body.
|
|
17
17
|
`;
|
|
18
|
+
const TASK_INPUT_ALIASES = ['任务输入', 'Task Input'];
|
|
18
19
|
const DESCRIPTION_ALIASES = ['描述', 'Description'];
|
|
19
20
|
const REQUIREMENTS_ALIASES = ['需求', 'Requirements'];
|
|
20
21
|
/**
|
|
@@ -22,17 +23,21 @@ const REQUIREMENTS_ALIASES = ['需求', 'Requirements'];
|
|
|
22
23
|
* task.md actually uses, with empty sections falling back to `N/A`.
|
|
23
24
|
*/
|
|
24
25
|
function buildDefaultBody(content) {
|
|
26
|
+
const taskInputHeading = findSectionHeading(content, TASK_INPUT_ALIASES);
|
|
25
27
|
const descHeading = findSectionHeading(content, DESCRIPTION_ALIASES);
|
|
26
28
|
const reqHeading = findSectionHeading(content, REQUIREMENTS_ALIASES);
|
|
29
|
+
const taskInput = extractSection(content, TASK_INPUT_ALIASES) || PLACEHOLDER;
|
|
27
30
|
const description = extractSection(content, DESCRIPTION_ALIASES) || PLACEHOLDER;
|
|
28
31
|
const requirements = extractSection(content, REQUIREMENTS_ALIASES) || PLACEHOLDER;
|
|
29
|
-
return `## ${descHeading}\n\n${description}\n\n## ${reqHeading}\n\n${requirements}\n`;
|
|
32
|
+
return `## ${taskInputHeading}\n\n${taskInput}\n\n## ${descHeading}\n\n${description}\n\n## ${reqHeading}\n\n${requirements}\n`;
|
|
30
33
|
}
|
|
31
34
|
function readTaskFields(content) {
|
|
32
35
|
return {
|
|
33
36
|
title: extractTitle(content),
|
|
34
37
|
description: extractSection(content, DESCRIPTION_ALIASES),
|
|
35
|
-
requirements: extractSection(content, REQUIREMENTS_ALIASES)
|
|
38
|
+
requirements: extractSection(content, REQUIREMENTS_ALIASES),
|
|
39
|
+
taskInput: extractSection(content, TASK_INPUT_ALIASES),
|
|
40
|
+
taskInputHeading: findSectionHeading(content, TASK_INPUT_ALIASES)
|
|
36
41
|
};
|
|
37
42
|
}
|
|
38
43
|
function issueBody(args = []) {
|
package/dist/lib/task/events.js
CHANGED
|
@@ -79,7 +79,7 @@ const FAMILY = {
|
|
|
79
79
|
'review-plan': { artifact: 'review-plan', started: ['technical-design', 'technical-design-review'], completed: ['technical-design', 'technical-design-review'], target: 'technical-design-review', label: 'Review Plan' },
|
|
80
80
|
code: { artifact: 'code', started: ['technical-design-review', 'code-review'], completed: ['technical-design-review', 'code-review'], target: 'code', label: 'Code Task' },
|
|
81
81
|
'review-code': { artifact: 'review-code', started: ['code', 'code-review', 'commit'], completed: ['code', 'code-review', 'commit'], target: 'code-review', label: 'Review Code' },
|
|
82
|
-
'manual-validation': { artifact: 'manual-validation', started: ['code-review'], completed: ['code-review'], target: null, label: 'Complete Manual Validation' }
|
|
82
|
+
'manual-validation': { artifact: 'manual-validation', started: ['code-review', 'commit'], completed: ['code-review', 'commit'], target: null, label: 'Complete Manual Validation' }
|
|
83
83
|
};
|
|
84
84
|
function eventParts(event) {
|
|
85
85
|
const [family, suffix] = event.split('.');
|
|
@@ -73,6 +73,8 @@ function renderTemplateBody(formText, fields) {
|
|
|
73
73
|
const value = mapped.trim() === '' ? PLACEHOLDER : mapped;
|
|
74
74
|
sections.push(`### ${label}\n\n${value}`);
|
|
75
75
|
}
|
|
76
|
+
const taskInput = fields.taskInput.trim() === '' ? PLACEHOLDER : fields.taskInput;
|
|
77
|
+
sections.push(`### ${fields.taskInputHeading}\n\n${taskInput}`);
|
|
76
78
|
return `${sections.join('\n\n')}\n`;
|
|
77
79
|
}
|
|
78
80
|
export { requirementFieldLabels, renderTemplateBody, mapFieldValue, PLACEHOLDER };
|
|
@@ -5,6 +5,15 @@ import { artifactName, maxRound } from "./review-artifacts.js";
|
|
|
5
5
|
// covers ALL tracked changes by default. This default denylist is EMPTY —
|
|
6
6
|
// projects opt into exclusions explicitly via review.post_review_exclude_globs.
|
|
7
7
|
export const DEFAULT_POST_REVIEW_EXCLUDES = [];
|
|
8
|
+
export function loadPostReviewConfig(repoRoot) {
|
|
9
|
+
try {
|
|
10
|
+
const config = JSON.parse(fs.readFileSync(path.join(repoRoot, ".agents", ".airc.json"), "utf8"));
|
|
11
|
+
return config.review && typeof config.review === "object" ? config.review : {};
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
8
17
|
export function resolvePostReviewGlobs(config = {}, reviewConfig = {}) {
|
|
9
18
|
const projectExcludes = [
|
|
10
19
|
...(Array.isArray(reviewConfig.post_review_exclude_globs) ? reviewConfig.post_review_exclude_globs : []),
|