@fitlab-ai/agent-infra 0.6.5 → 0.7.0
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 +51 -25
- package/README.zh-CN.md +49 -23
- package/dist/lib/defaults.json +1 -0
- package/dist/lib/init.js +3 -0
- package/dist/lib/sandbox/commands/create.js +4 -2
- package/dist/lib/sandbox/commands/enter.js +15 -4
- package/dist/lib/sandbox/commands/list-running.js +108 -0
- package/dist/lib/sandbox/commands/ls.js +24 -45
- package/dist/lib/sandbox/commands/rebuild.js +4 -2
- package/dist/lib/sandbox/config.js +3 -0
- package/dist/lib/sandbox/index.js +2 -1
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/dist/lib/sandbox/tools.js +213 -8
- package/dist/lib/update.js +12 -1
- package/lib/defaults.json +1 -0
- package/lib/init.ts +10 -0
- package/lib/sandbox/commands/create.ts +10 -2
- package/lib/sandbox/commands/enter.ts +14 -4
- package/lib/sandbox/commands/list-running.ts +135 -0
- package/lib/sandbox/commands/ls.ts +28 -49
- package/lib/sandbox/commands/rebuild.ts +9 -2
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/index.ts +2 -1
- package/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/lib/sandbox/tools.ts +248 -9
- package/lib/update.ts +15 -1
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +1 -1
- package/templates/.agents/QUICKSTART.zh-CN.md +1 -1
- package/templates/.agents/README.en.md +79 -2
- package/templates/.agents/README.zh-CN.md +79 -2
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +6 -5
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +6 -5
- package/templates/.agents/rules/milestone-inference.github.en.md +2 -2
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +2 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +57 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +57 -0
- package/templates/.agents/rules/pr-sync.github.en.md +4 -5
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +4 -5
- package/templates/.agents/rules/task-management.en.md +9 -6
- package/templates/.agents/rules/task-management.zh-CN.md +9 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -2
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -2
- package/templates/.agents/scripts/validate-artifact.js +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +16 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +16 -4
- package/templates/.agents/skills/check-task/SKILL.en.md +43 -32
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +42 -31
- package/templates/.agents/skills/code-task/SKILL.en.md +117 -0
- package/templates/.agents/skills/{implement-task → code-task}/SKILL.zh-CN.md +51 -24
- package/templates/.agents/skills/{implement-task → code-task}/config/verify.en.json +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/config/verify.zh-CN.json +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.zh-CN.md +2 -2
- package/templates/.agents/skills/{implement-task/reference/implementation-rules.en.md → code-task/reference/code-rules.en.md} +6 -6
- package/templates/.agents/skills/{implement-task/reference/implementation-rules.zh-CN.md → code-task/reference/code-rules.zh-CN.md} +3 -3
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +69 -0
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +69 -0
- package/templates/.agents/skills/{refine-task/reference/fix-workflow.en.md → code-task/reference/fix-mode.en.md} +12 -12
- package/templates/.agents/skills/{refine-task/reference/fix-workflow.zh-CN.md → code-task/reference/fix-mode.zh-CN.md} +8 -8
- package/templates/.agents/skills/code-task/reference/output-template.en.md +20 -0
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +20 -0
- package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.en.md +4 -4
- package/templates/.agents/skills/{implement-task → code-task}/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/scripts/detect-mode.js +370 -0
- package/templates/.agents/skills/commit/SKILL.en.md +2 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +10 -6
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +10 -6
- package/templates/.agents/skills/complete-task/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +17 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +17 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/import-issue/SKILL.en.md +3 -3
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/restore-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +76 -0
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +102 -0
- package/templates/.agents/skills/review-analysis/config/verify.en.json +51 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +51 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +87 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +87 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +90 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +91 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +47 -0
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +47 -0
- package/templates/.agents/skills/{review-task → review-code}/SKILL.en.md +11 -9
- package/templates/.agents/skills/{review-task → review-code}/SKILL.zh-CN.md +15 -9
- package/templates/.agents/skills/{review-task → review-code}/config/verify.en.json +7 -5
- package/templates/.agents/skills/{review-task → review-code}/config/verify.zh-CN.json +6 -4
- package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.en.md +21 -17
- package/templates/.agents/skills/{review-task → review-code}/reference/output-templates.zh-CN.md +19 -15
- package/templates/.agents/skills/{review-task → review-code}/reference/report-template.en.md +5 -6
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +91 -0
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +48 -0
- package/templates/.agents/skills/{review-task → review-code}/reference/review-criteria.zh-CN.md +10 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +76 -0
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +102 -0
- package/templates/.agents/skills/{refine-task → review-plan}/config/verify.en.json +14 -10
- package/templates/.agents/skills/{refine-task → review-plan}/config/verify.zh-CN.json +14 -10
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +87 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +87 -0
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +90 -0
- package/templates/.agents/skills/{review-task → review-plan}/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +47 -0
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +47 -0
- package/templates/.agents/skills/test/SKILL.en.md +2 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -31
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +1 -0
- package/templates/.agents/templates/task.en.md +3 -3
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +126 -80
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +90 -44
- package/templates/.agents/workflows/feature-development.en.yaml +115 -70
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +92 -47
- package/templates/.agents/workflows/refactoring.en.yaml +123 -78
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +89 -44
- package/templates/.claude/commands/code-task.en.md +8 -0
- package/templates/.claude/commands/code-task.zh-CN.md +8 -0
- package/templates/.claude/commands/review-analysis.en.md +8 -0
- package/templates/.claude/commands/review-analysis.zh-CN.md +8 -0
- package/templates/.claude/commands/review-code.en.md +8 -0
- package/templates/.claude/commands/review-code.zh-CN.md +8 -0
- package/templates/.claude/commands/review-plan.en.md +8 -0
- package/templates/.claude/commands/review-plan.zh-CN.md +8 -0
- package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/code-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +1 -1
- package/templates/.gemini/commands/_project_/review-analysis.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/review-code.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +8 -0
- package/templates/.gemini/commands/_project_/review-plan.en.toml +8 -0
- package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +8 -0
- package/templates/.opencode/commands/code-task.en.md +11 -0
- package/templates/.opencode/commands/code-task.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-analysis.en.md +11 -0
- package/templates/.opencode/commands/review-analysis.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-code.en.md +11 -0
- package/templates/.opencode/commands/review-code.zh-CN.md +11 -0
- package/templates/.opencode/commands/review-plan.en.md +11 -0
- package/templates/.opencode/commands/review-plan.zh-CN.md +11 -0
- package/templates/.agents/skills/implement-task/SKILL.en.md +0 -173
- package/templates/.agents/skills/implement-task/reference/output-template.en.md +0 -20
- package/templates/.agents/skills/implement-task/reference/output-template.zh-CN.md +0 -20
- package/templates/.agents/skills/refine-task/SKILL.en.md +0 -153
- package/templates/.agents/skills/refine-task/SKILL.zh-CN.md +0 -153
- package/templates/.agents/skills/refine-task/reference/report-template.en.md +0 -64
- package/templates/.agents/skills/refine-task/reference/report-template.zh-CN.md +0 -64
- package/templates/.agents/skills/review-task/reference/review-criteria.en.md +0 -42
- package/templates/.claude/commands/implement-task.en.md +0 -8
- package/templates/.claude/commands/implement-task.zh-CN.md +0 -8
- package/templates/.claude/commands/refine-task.en.md +0 -8
- package/templates/.claude/commands/refine-task.zh-CN.md +0 -8
- package/templates/.claude/commands/review-task.en.md +0 -8
- package/templates/.claude/commands/review-task.zh-CN.md +0 -8
- package/templates/.gemini/commands/_project_/implement-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/implement-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/review-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/review-task.zh-CN.toml +0 -8
- package/templates/.opencode/commands/implement-task.en.md +0 -11
- package/templates/.opencode/commands/implement-task.zh-CN.md +0 -11
- package/templates/.opencode/commands/refine-task.en.md +0 -11
- package/templates/.opencode/commands/refine-task.zh-CN.md +0 -11
- package/templates/.opencode/commands/review-task.en.md +0 -11
- package/templates/.opencode/commands/review-task.zh-CN.md +0 -11
- /package/templates/.agents/skills/{implement-task → code-task}/reference/branch-management.en.md +0 -0
|
@@ -5,51 +5,36 @@ import pc from 'picocolors';
|
|
|
5
5
|
import { loadConfig } from '../config.ts';
|
|
6
6
|
import { sandboxBranchLabel, sandboxLabel } from '../constants.ts';
|
|
7
7
|
import { detectEngine } from '../engine.ts';
|
|
8
|
-
import { runSafeEngine } from '../shell.ts';
|
|
9
8
|
import { resolveTools, toolProjectDirCandidates } from '../tools.ts';
|
|
9
|
+
import { fetchSandboxRows } from './list-running.ts';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export { containerListFormat, parseLabels } from './list-running.ts';
|
|
12
|
+
|
|
13
|
+
const USAGE = `Usage: ai sandbox ls
|
|
14
|
+
|
|
15
|
+
Lists all containers for the current project. The leftmost '#' column
|
|
16
|
+
numbers running sandboxes; use it as "ai sandbox exec '#N'" to enter one.
|
|
17
|
+
Quote '#N' to avoid shell '#' comment handling.`;
|
|
18
|
+
|
|
19
|
+
const CONTAINER_TABLE_HEADERS = ['#', 'NAMES', 'STATUS', 'BRANCH'] as const;
|
|
13
20
|
|
|
14
21
|
type ContainerTableRow = {
|
|
22
|
+
index: string;
|
|
15
23
|
name: string;
|
|
16
24
|
status: string;
|
|
17
25
|
branch: string;
|
|
18
26
|
};
|
|
19
27
|
|
|
20
|
-
// Exported to lock the docker/podman-compatible format in unit tests.
|
|
21
|
-
export function containerListFormat(): string {
|
|
22
|
-
return '{{.Names}}\t{{.Status}}\t{{.Labels}}';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function parseLabels(csv: string): Record<string, string> {
|
|
26
|
-
if (!csv) {
|
|
27
|
-
return {};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const labels: Record<string, string> = {};
|
|
31
|
-
for (const pair of csv.split(',')) {
|
|
32
|
-
if (!pair) {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const eq = pair.indexOf('=');
|
|
36
|
-
if (eq < 0) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
40
|
-
}
|
|
41
|
-
return labels;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
28
|
export function formatContainerTable(rows: ContainerTableRow[]): string[] {
|
|
45
|
-
const columns = rows.map((row) => [row.name, row.status, row.branch] as const);
|
|
29
|
+
const columns = rows.map((row) => [row.index, row.name, row.status, row.branch] as const);
|
|
46
30
|
const widths = [
|
|
47
|
-
Math.max(CONTAINER_TABLE_HEADERS[0].length, ...rows.map((row) => row.
|
|
48
|
-
Math.max(CONTAINER_TABLE_HEADERS[1].length, ...rows.map((row) => row.
|
|
49
|
-
Math.max(CONTAINER_TABLE_HEADERS[2].length, ...rows.map((row) => row.
|
|
31
|
+
Math.max(CONTAINER_TABLE_HEADERS[0].length, ...rows.map((row) => row.index.length)),
|
|
32
|
+
Math.max(CONTAINER_TABLE_HEADERS[1].length, ...rows.map((row) => row.name.length)),
|
|
33
|
+
Math.max(CONTAINER_TABLE_HEADERS[2].length, ...rows.map((row) => row.status.length)),
|
|
34
|
+
Math.max(CONTAINER_TABLE_HEADERS[3].length, ...rows.map((row) => row.branch.length))
|
|
50
35
|
] as const;
|
|
51
|
-
const renderRow = (values: readonly [string, string, string]): string =>
|
|
52
|
-
`${values[0].padEnd(widths[0])} ${values[1].padEnd(widths[1])} ${values[2]}`.trimEnd();
|
|
36
|
+
const renderRow = (values: readonly [string, string, string, string]): string =>
|
|
37
|
+
`${values[0].padEnd(widths[0])} ${values[1].padEnd(widths[1])} ${values[2].padEnd(widths[2])} ${values[3]}`.trimEnd();
|
|
53
38
|
|
|
54
39
|
return [
|
|
55
40
|
renderRow(CONTAINER_TABLE_HEADERS),
|
|
@@ -75,28 +60,22 @@ export function ls(args: string[] = []): void {
|
|
|
75
60
|
const engine = detectEngine(config);
|
|
76
61
|
const tools = resolveTools(config);
|
|
77
62
|
const label = sandboxLabel(config);
|
|
78
|
-
const
|
|
79
|
-
'ps',
|
|
80
|
-
'-a',
|
|
81
|
-
'--filter',
|
|
82
|
-
`label=${label}`,
|
|
83
|
-
'--format',
|
|
84
|
-
containerListFormat()
|
|
85
|
-
]);
|
|
63
|
+
const { running, nonRunning } = fetchSandboxRows(engine, label, sandboxBranchLabel(config));
|
|
86
64
|
|
|
87
65
|
p.intro(pc.cyan(`Sandbox status for ${config.project}`));
|
|
88
66
|
|
|
89
67
|
p.log.step('Containers');
|
|
90
|
-
|
|
68
|
+
const ordered = [...running, ...nonRunning];
|
|
69
|
+
if (ordered.length === 0) {
|
|
91
70
|
p.log.warn(' No sandbox containers');
|
|
92
71
|
} else {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
});
|
|
99
|
-
for (const line of formatContainerTable(
|
|
72
|
+
const tableRows: ContainerTableRow[] = ordered.map((row) => ({
|
|
73
|
+
index: row.index === null ? '' : String(row.index),
|
|
74
|
+
name: row.name,
|
|
75
|
+
status: row.status,
|
|
76
|
+
branch: row.branch
|
|
77
|
+
}));
|
|
78
|
+
for (const line of formatContainerTable(tableRows)) {
|
|
100
79
|
process.stdout.write(` ${line}\n`);
|
|
101
80
|
}
|
|
102
81
|
}
|
|
@@ -8,7 +8,12 @@ import { prepareDockerfile } from '../dockerfile.ts';
|
|
|
8
8
|
import { sandboxImageConfigLabel, sandboxLabel } from '../constants.ts';
|
|
9
9
|
import { detectEngine, ensureDocker } from '../engine.ts';
|
|
10
10
|
import { runEngine, runOkEngine, runSafeEngine, runVerboseEngine } from '../shell.ts';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
imageSignatureFields,
|
|
13
|
+
resolveTools,
|
|
14
|
+
toolNpmPackagesArg,
|
|
15
|
+
toolShellInstallScriptBase64
|
|
16
|
+
} from '../tools.ts';
|
|
12
17
|
import type { SandboxTool } from '../tools.ts';
|
|
13
18
|
import { toEnginePath } from '../engines/wsl2-paths.ts';
|
|
14
19
|
import { resolveBuildUid } from '../engines/native.ts';
|
|
@@ -23,7 +28,7 @@ function buildSignature(preparedDockerfile: PreparedDockerfile, tools: SandboxTo
|
|
|
23
28
|
return createHash('sha256')
|
|
24
29
|
.update(JSON.stringify({
|
|
25
30
|
dockerfile: preparedDockerfile.signature,
|
|
26
|
-
tools: tools
|
|
31
|
+
tools: imageSignatureFields(tools)
|
|
27
32
|
}))
|
|
28
33
|
.digest('hex')
|
|
29
34
|
.slice(0, 12);
|
|
@@ -66,6 +71,8 @@ export function buildArgs(
|
|
|
66
71
|
`HOST_GID=${hostGid}`,
|
|
67
72
|
'--build-arg',
|
|
68
73
|
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
74
|
+
'--build-arg',
|
|
75
|
+
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
69
76
|
'--label',
|
|
70
77
|
sandboxLabel(config),
|
|
71
78
|
'--label',
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -6,6 +6,8 @@ import pc from 'picocolors';
|
|
|
6
6
|
import { validateSandboxEngine } from './engine.ts';
|
|
7
7
|
import { hostJoin } from './engines/wsl2-paths.ts';
|
|
8
8
|
import { findRuntimeEngineMismatches } from './runtime-engines.ts';
|
|
9
|
+
import { parseCustomTools } from './tools.ts';
|
|
10
|
+
import type { SandboxTool } from './tools.ts';
|
|
9
11
|
|
|
10
12
|
const DEFAULTS = Object.freeze({
|
|
11
13
|
engine: null,
|
|
@@ -26,6 +28,7 @@ type SandboxConfigInput = {
|
|
|
26
28
|
engine?: string | null;
|
|
27
29
|
runtimes?: string[];
|
|
28
30
|
tools?: string[];
|
|
31
|
+
customTools?: unknown;
|
|
29
32
|
dockerfile?: string | null;
|
|
30
33
|
vm?: Record<string, unknown>;
|
|
31
34
|
};
|
|
@@ -51,6 +54,7 @@ export type SandboxConfig = {
|
|
|
51
54
|
engine: string | null;
|
|
52
55
|
runtimes: string[];
|
|
53
56
|
tools: string[];
|
|
57
|
+
customTools: SandboxTool[];
|
|
54
58
|
dockerfile: string | null;
|
|
55
59
|
vm: SandboxVmConfig;
|
|
56
60
|
};
|
|
@@ -136,6 +140,8 @@ export function loadConfig({
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
const customTools = parseCustomTools(sandbox.customTools, { home });
|
|
144
|
+
|
|
139
145
|
return {
|
|
140
146
|
repoRoot,
|
|
141
147
|
configPath,
|
|
@@ -153,6 +159,7 @@ export function loadConfig({
|
|
|
153
159
|
tools: Array.isArray(sandbox.tools) && sandbox.tools.length > 0
|
|
154
160
|
? [...sandbox.tools]
|
|
155
161
|
: defaults.tools,
|
|
162
|
+
customTools,
|
|
156
163
|
dockerfile,
|
|
157
164
|
vm: {
|
|
158
165
|
cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
|
package/lib/sandbox/index.ts
CHANGED
|
@@ -2,7 +2,8 @@ const USAGE = `Usage: ai sandbox <command> [options]
|
|
|
2
2
|
|
|
3
3
|
Commands:
|
|
4
4
|
create <branch> [base] Create a sandbox (VM + image + worktree + container)
|
|
5
|
-
exec <branch> [cmd...]
|
|
5
|
+
exec <branch | '#N'> [cmd...]
|
|
6
|
+
Enter sandbox or run a command (use leftmost '#' column from 'ls')
|
|
6
7
|
ls List sandboxes for the current project
|
|
7
8
|
prune [--dry-run] Remove orphaned per-branch state dirs
|
|
8
9
|
rebuild [--quiet] [--refresh]
|
|
@@ -4,16 +4,20 @@ ENV OPENCODE_DISABLE_AUTOUPDATE=1
|
|
|
4
4
|
ENV NPM_CONFIG_PREFIX=/home/devuser/.npm-global
|
|
5
5
|
ENV PATH="/home/devuser/.npm-global/bin:${PATH}"
|
|
6
6
|
|
|
7
|
-
ARG AI_TOOL_PACKAGES
|
|
8
|
-
RUN
|
|
9
|
-
echo "AI_TOOL_PACKAGES build arg is required"; \
|
|
10
|
-
exit 1; \
|
|
11
|
-
fi && \
|
|
12
|
-
set -e && \
|
|
7
|
+
ARG AI_TOOL_PACKAGES=
|
|
8
|
+
RUN set -e && \
|
|
13
9
|
for pkg in ${AI_TOOL_PACKAGES}; do \
|
|
14
10
|
npm install -g "$pkg"; \
|
|
15
11
|
done
|
|
16
12
|
|
|
13
|
+
ARG AI_TOOLS_SHELL_INSTALL_B64=
|
|
14
|
+
RUN if [ -n "${AI_TOOLS_SHELL_INSTALL_B64}" ]; then \
|
|
15
|
+
set -e && \
|
|
16
|
+
echo "${AI_TOOLS_SHELL_INSTALL_B64}" | base64 -d > /tmp/ai-tools-install.sh && \
|
|
17
|
+
bash /tmp/ai-tools-install.sh && \
|
|
18
|
+
rm /tmp/ai-tools-install.sh; \
|
|
19
|
+
fi
|
|
20
|
+
|
|
17
21
|
RUN npm install -g pyright
|
|
18
22
|
|
|
19
23
|
RUN mkdir -p /home/devuser/.local/share /home/devuser/.local/state
|
package/lib/sandbox/tools.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { safeNameCandidates, sanitizeBranchName } from './constants.ts';
|
|
2
2
|
import { hostJoin } from './engines/wsl2-paths.ts';
|
|
3
3
|
|
|
4
|
+
export type SandboxToolInstall =
|
|
5
|
+
| { type: 'npm'; cmd: string }
|
|
6
|
+
| { type: 'shell'; cmd: string };
|
|
7
|
+
|
|
4
8
|
export type SandboxTool = {
|
|
5
9
|
id: string;
|
|
6
10
|
name: string;
|
|
7
|
-
|
|
11
|
+
install: SandboxToolInstall;
|
|
8
12
|
sandboxBase: string;
|
|
9
13
|
containerMount: string;
|
|
10
14
|
versionCmd: string;
|
|
@@ -21,14 +25,17 @@ type ToolsConfig = {
|
|
|
21
25
|
home: string;
|
|
22
26
|
project: string;
|
|
23
27
|
tools: string[];
|
|
28
|
+
customTools?: SandboxTool[];
|
|
24
29
|
};
|
|
25
30
|
|
|
31
|
+
const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
32
|
+
|
|
26
33
|
function createBuiltinTools(home: string, project: string): Record<string, SandboxTool> {
|
|
27
34
|
return {
|
|
28
35
|
'claude-code': {
|
|
29
36
|
id: 'claude-code',
|
|
30
37
|
name: 'Claude Code',
|
|
31
|
-
|
|
38
|
+
install: { type: 'npm', cmd: '@anthropic-ai/claude-code@stable' },
|
|
32
39
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'claude-code'),
|
|
33
40
|
containerMount: '/home/devuser/.claude',
|
|
34
41
|
versionCmd: 'claude --version',
|
|
@@ -58,7 +65,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
58
65
|
codex: {
|
|
59
66
|
id: 'codex',
|
|
60
67
|
name: 'Codex',
|
|
61
|
-
|
|
68
|
+
install: { type: 'npm', cmd: '@openai/codex' },
|
|
62
69
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'codex'),
|
|
63
70
|
containerMount: '/home/devuser/.codex',
|
|
64
71
|
versionCmd: 'codex --version',
|
|
@@ -73,7 +80,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
73
80
|
opencode: {
|
|
74
81
|
id: 'opencode',
|
|
75
82
|
name: 'OpenCode',
|
|
76
|
-
|
|
83
|
+
install: { type: 'npm', cmd: 'opencode-ai' },
|
|
77
84
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'opencode'),
|
|
78
85
|
containerMount: '/home/devuser/.local/share/opencode',
|
|
79
86
|
versionCmd: 'opencode version',
|
|
@@ -92,7 +99,7 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
92
99
|
'gemini-cli': {
|
|
93
100
|
id: 'gemini-cli',
|
|
94
101
|
name: 'Gemini CLI',
|
|
95
|
-
|
|
102
|
+
install: { type: 'npm', cmd: '@google/gemini-cli' },
|
|
96
103
|
sandboxBase: hostJoin(home, '.agent-infra', 'sandboxes', 'gemini-cli'),
|
|
97
104
|
containerMount: '/home/devuser/.gemini',
|
|
98
105
|
versionCmd: 'gemini --version',
|
|
@@ -108,16 +115,224 @@ function createBuiltinTools(home: string, project: string): Record<string, Sandb
|
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
117
|
|
|
118
|
+
export function builtinToolIds(): string[] {
|
|
119
|
+
return Object.keys(createBuiltinTools('', ''));
|
|
120
|
+
}
|
|
121
|
+
|
|
111
122
|
function validateTool(tool: SandboxTool): void {
|
|
112
|
-
if (!tool.
|
|
113
|
-
throw new Error(`Invalid sandbox tool
|
|
123
|
+
if (!tool.id || !TOOL_ID_PATTERN.test(tool.id)) {
|
|
124
|
+
throw new Error(`Invalid sandbox tool id: ${String(tool.id)}`);
|
|
125
|
+
}
|
|
126
|
+
if (!tool.install || (tool.install.type !== 'npm' && tool.install.type !== 'shell')) {
|
|
127
|
+
throw new Error(`Sandbox tool ${tool.id} has invalid install.type`);
|
|
128
|
+
}
|
|
129
|
+
if (!tool.install.cmd) {
|
|
130
|
+
throw new Error(`Sandbox tool ${tool.id} has empty install.cmd`);
|
|
131
|
+
}
|
|
132
|
+
if (!tool.containerMount || !tool.containerMount.startsWith('/')) {
|
|
133
|
+
throw new Error(`Sandbox tool ${tool.id} containerMount must be an absolute path`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
138
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function asString(value: unknown, field: string, context: string): string {
|
|
142
|
+
if (typeof value !== 'string') {
|
|
143
|
+
throw new Error(`${context}: field "${field}" must be a string`);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function asOptionalNonEmptyString(value: unknown, field: string, context: string): string | undefined {
|
|
149
|
+
if (value === undefined) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
if (typeof value !== 'string') {
|
|
153
|
+
throw new Error(`${context}: field "${field}" must be a string when provided`);
|
|
154
|
+
}
|
|
155
|
+
if (value.length === 0) {
|
|
156
|
+
throw new Error(`${context}: field "${field}" must be non-empty when provided`);
|
|
157
|
+
}
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function asStringRecord(value: unknown, field: string, context: string): Record<string, string> | undefined {
|
|
162
|
+
if (value === undefined) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
if (!isPlainObject(value)) {
|
|
166
|
+
throw new Error(`${context}: field "${field}" must be an object when provided`);
|
|
167
|
+
}
|
|
168
|
+
const out: Record<string, string> = {};
|
|
169
|
+
for (const [key, val] of Object.entries(value)) {
|
|
170
|
+
if (typeof val !== 'string') {
|
|
171
|
+
throw new Error(`${context}: field "${field}.${key}" must be a string`);
|
|
172
|
+
}
|
|
173
|
+
out[key] = val;
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function asStringArray(value: unknown, field: string, context: string): string[] | undefined {
|
|
179
|
+
if (value === undefined) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
if (!Array.isArray(value)) {
|
|
183
|
+
throw new Error(`${context}: field "${field}" must be an array when provided`);
|
|
184
|
+
}
|
|
185
|
+
return value.map((item, index) => {
|
|
186
|
+
if (typeof item !== 'string') {
|
|
187
|
+
throw new Error(`${context}: field "${field}[${index}]" must be a string`);
|
|
188
|
+
}
|
|
189
|
+
return item;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function parseInstall(value: unknown, context: string): SandboxToolInstall {
|
|
194
|
+
if (!isPlainObject(value)) {
|
|
195
|
+
throw new Error(`${context}: field "install" must be an object`);
|
|
196
|
+
}
|
|
197
|
+
const type = value.type;
|
|
198
|
+
if (type !== 'npm' && type !== 'shell') {
|
|
199
|
+
throw new Error(`${context}: field "install.type" must be "npm" or "shell"`);
|
|
200
|
+
}
|
|
201
|
+
const cmd = asString(value.cmd, 'install.cmd', context);
|
|
202
|
+
if (!cmd) {
|
|
203
|
+
throw new Error(`${context}: field "install.cmd" must be non-empty`);
|
|
204
|
+
}
|
|
205
|
+
return { type, cmd };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function parseHostPreSeedFiles(value: unknown, context: string): SandboxTool['hostPreSeedFiles'] {
|
|
209
|
+
if (value === undefined) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
if (!Array.isArray(value)) {
|
|
213
|
+
throw new Error(`${context}: field "hostPreSeedFiles" must be an array when provided`);
|
|
214
|
+
}
|
|
215
|
+
return value.map((item, index) => {
|
|
216
|
+
if (!isPlainObject(item)) {
|
|
217
|
+
throw new Error(`${context}: field "hostPreSeedFiles[${index}]" must be an object`);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
hostPath: asString(item.hostPath, `hostPreSeedFiles[${index}].hostPath`, context),
|
|
221
|
+
sandboxName: asString(item.sandboxName, `hostPreSeedFiles[${index}].sandboxName`, context)
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function parseHostPreSeedDirs(value: unknown, context: string): SandboxTool['hostPreSeedDirs'] {
|
|
227
|
+
if (value === undefined) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
if (!Array.isArray(value)) {
|
|
231
|
+
throw new Error(`${context}: field "hostPreSeedDirs" must be an array when provided`);
|
|
114
232
|
}
|
|
233
|
+
return value.map((item, index) => {
|
|
234
|
+
if (!isPlainObject(item)) {
|
|
235
|
+
throw new Error(`${context}: field "hostPreSeedDirs[${index}]" must be an object`);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
hostDir: asString(item.hostDir, `hostPreSeedDirs[${index}].hostDir`, context),
|
|
239
|
+
sandboxSubdir: asString(item.sandboxSubdir, `hostPreSeedDirs[${index}].sandboxSubdir`, context)
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function parseHostLiveMounts(value: unknown, context: string): SandboxTool['hostLiveMounts'] {
|
|
245
|
+
if (value === undefined) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
if (!Array.isArray(value)) {
|
|
249
|
+
throw new Error(`${context}: field "hostLiveMounts" must be an array when provided`);
|
|
250
|
+
}
|
|
251
|
+
return value.map((item, index) => {
|
|
252
|
+
if (!isPlainObject(item)) {
|
|
253
|
+
throw new Error(`${context}: field "hostLiveMounts[${index}]" must be an object`);
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
hostPath: asString(item.hostPath, `hostLiveMounts[${index}].hostPath`, context),
|
|
257
|
+
containerSubpath: asString(item.containerSubpath, `hostLiveMounts[${index}].containerSubpath`, context)
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function parseCustomTool(
|
|
263
|
+
entry: unknown,
|
|
264
|
+
index: number,
|
|
265
|
+
options: { home: string }
|
|
266
|
+
): SandboxTool {
|
|
267
|
+
const context = `customTools[${index}]`;
|
|
268
|
+
if (!isPlainObject(entry)) {
|
|
269
|
+
throw new Error(`${context} must be an object`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const id = asString(entry.id, 'id', context);
|
|
273
|
+
if (!TOOL_ID_PATTERN.test(id)) {
|
|
274
|
+
throw new Error(`${context}: field "id" must match ${TOOL_ID_PATTERN.source}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const containerMount = asOptionalNonEmptyString(entry.containerMount, 'containerMount', context)
|
|
278
|
+
?? `/home/devuser/.${id}`;
|
|
279
|
+
if (!containerMount.startsWith('/')) {
|
|
280
|
+
throw new Error(`${context}: field "containerMount" must be an absolute path`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const tool: SandboxTool = {
|
|
284
|
+
id,
|
|
285
|
+
name: asOptionalNonEmptyString(entry.name, 'name', context) ?? id,
|
|
286
|
+
install: parseInstall(entry.install, context),
|
|
287
|
+
sandboxBase: hostJoin(options.home, '.agent-infra', 'sandboxes', id),
|
|
288
|
+
containerMount,
|
|
289
|
+
versionCmd: asOptionalNonEmptyString(entry.versionCmd, 'versionCmd', context) ?? `which ${id}`,
|
|
290
|
+
setupHint: asOptionalNonEmptyString(entry.setupHint, 'setupHint', context)
|
|
291
|
+
?? `Run \`${id}\` inside the container to set up.`,
|
|
292
|
+
envVars: asStringRecord(entry.envVars, 'envVars', context),
|
|
293
|
+
hostPreSeedFiles: parseHostPreSeedFiles(entry.hostPreSeedFiles, context),
|
|
294
|
+
hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
|
|
295
|
+
pathRewriteFiles: asStringArray(entry.pathRewriteFiles, 'pathRewriteFiles', context),
|
|
296
|
+
hostLiveMounts: parseHostLiveMounts(entry.hostLiveMounts, context),
|
|
297
|
+
postSetupCmds: asStringArray(entry.postSetupCmds, 'postSetupCmds', context)
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
validateTool(tool);
|
|
301
|
+
return tool;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function parseCustomTools(value: unknown, options: { home: string }): SandboxTool[] {
|
|
305
|
+
if (value === undefined || value === null) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
if (!Array.isArray(value)) {
|
|
309
|
+
throw new Error('sandbox: "customTools" must be an array');
|
|
310
|
+
}
|
|
311
|
+
return value.map((entry, index) => parseCustomTool(entry, index, options));
|
|
115
312
|
}
|
|
116
313
|
|
|
117
314
|
export function resolveTools(config: ToolsConfig): SandboxTool[] {
|
|
118
315
|
const builtins = createBuiltinTools(config.home, config.project);
|
|
316
|
+
const customs = config.customTools ?? [];
|
|
317
|
+
|
|
318
|
+
const seen = new Set<string>();
|
|
319
|
+
for (const tool of customs) {
|
|
320
|
+
if (builtins[tool.id]) {
|
|
321
|
+
throw new Error(`Custom sandbox tool id "${tool.id}" collides with a built-in tool`);
|
|
322
|
+
}
|
|
323
|
+
if (seen.has(tool.id)) {
|
|
324
|
+
throw new Error(`Duplicate sandbox tool id "${tool.id}" in customTools`);
|
|
325
|
+
}
|
|
326
|
+
seen.add(tool.id);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const merged: Record<string, SandboxTool> = { ...builtins };
|
|
330
|
+
for (const tool of customs) {
|
|
331
|
+
merged[tool.id] = tool;
|
|
332
|
+
}
|
|
333
|
+
|
|
119
334
|
return config.tools.map((id) => {
|
|
120
|
-
const tool =
|
|
335
|
+
const tool = merged[id];
|
|
121
336
|
if (!tool) {
|
|
122
337
|
throw new Error(`Unknown sandbox tool: ${id}`);
|
|
123
338
|
}
|
|
@@ -139,5 +354,29 @@ export function toolProjectDirCandidates(tool: SandboxTool, project: string): st
|
|
|
139
354
|
}
|
|
140
355
|
|
|
141
356
|
export function toolNpmPackagesArg(tools: SandboxTool[]): string {
|
|
142
|
-
return tools
|
|
357
|
+
return tools
|
|
358
|
+
.filter((tool) => tool.install.type === 'npm')
|
|
359
|
+
.map((tool) => tool.install.cmd)
|
|
360
|
+
.join(' ');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function toolShellInstallScript(tools: SandboxTool[]): string {
|
|
364
|
+
const blocks = tools
|
|
365
|
+
.filter((tool) => tool.install.type === 'shell')
|
|
366
|
+
.map((tool) => `# install: ${tool.id}\n${tool.install.cmd}`);
|
|
367
|
+
|
|
368
|
+
if (blocks.length === 0) {
|
|
369
|
+
return '';
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return ['#!/bin/bash', 'set -e', '', ...blocks, ''].join('\n');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function toolShellInstallScriptBase64(tools: SandboxTool[]): string {
|
|
376
|
+
const script = toolShellInstallScript(tools);
|
|
377
|
+
return script ? Buffer.from(script, 'utf8').toString('base64') : '';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function imageSignatureFields(tools: SandboxTool[]): Array<{ id: string; install: SandboxToolInstall }> {
|
|
381
|
+
return tools.map((tool) => ({ id: tool.id, install: tool.install }));
|
|
143
382
|
}
|
package/lib/update.ts
CHANGED
|
@@ -15,6 +15,7 @@ type UpdateConfig = {
|
|
|
15
15
|
org: string;
|
|
16
16
|
language: string;
|
|
17
17
|
platform?: { type?: string };
|
|
18
|
+
requiresPullRequest?: boolean;
|
|
18
19
|
sandbox?: Record<string, unknown>;
|
|
19
20
|
labels?: Record<string, unknown>;
|
|
20
21
|
files?: Partial<FileRegistry>;
|
|
@@ -22,6 +23,7 @@ type UpdateConfig = {
|
|
|
22
23
|
|
|
23
24
|
type Defaults = {
|
|
24
25
|
platform: { type: string };
|
|
26
|
+
requiresPullRequest: boolean;
|
|
25
27
|
sandbox: Record<string, unknown>;
|
|
26
28
|
labels: Record<string, unknown>;
|
|
27
29
|
files: FileRegistry;
|
|
@@ -178,6 +180,7 @@ async function cmdUpdate(): Promise<void> {
|
|
|
178
180
|
const platformAdded = !config.platform;
|
|
179
181
|
const sandboxAdded = !config.sandbox;
|
|
180
182
|
const labelsAdded = !config.labels;
|
|
183
|
+
const requiresPullRequestAdded = config.requiresPullRequest === undefined;
|
|
181
184
|
let configChanged = changed;
|
|
182
185
|
|
|
183
186
|
if (platformAdded) {
|
|
@@ -195,6 +198,11 @@ async function cmdUpdate(): Promise<void> {
|
|
|
195
198
|
configChanged = true;
|
|
196
199
|
}
|
|
197
200
|
|
|
201
|
+
if (requiresPullRequestAdded) {
|
|
202
|
+
config.requiresPullRequest = defaults.requiresPullRequest;
|
|
203
|
+
configChanged = true;
|
|
204
|
+
}
|
|
205
|
+
|
|
198
206
|
if (configChanged) {
|
|
199
207
|
console.log('');
|
|
200
208
|
if (hasNewEntries) {
|
|
@@ -205,7 +213,7 @@ async function cmdUpdate(): Promise<void> {
|
|
|
205
213
|
for (const entry of added.merged) {
|
|
206
214
|
ok(` merged: ${entry}`);
|
|
207
215
|
}
|
|
208
|
-
} else if (platformAdded || sandboxAdded || labelsAdded) {
|
|
216
|
+
} else if (platformAdded || sandboxAdded || labelsAdded || requiresPullRequestAdded) {
|
|
209
217
|
if (platformAdded) {
|
|
210
218
|
info(`Default platform config added to ${CONFIG_PATH}.`);
|
|
211
219
|
}
|
|
@@ -215,6 +223,9 @@ async function cmdUpdate(): Promise<void> {
|
|
|
215
223
|
if (labelsAdded) {
|
|
216
224
|
info(`Default labels.in config added to ${CONFIG_PATH}.`);
|
|
217
225
|
}
|
|
226
|
+
if (requiresPullRequestAdded) {
|
|
227
|
+
info(`Default requiresPullRequest=${defaults.requiresPullRequest} added to ${CONFIG_PATH}.`);
|
|
228
|
+
}
|
|
218
229
|
} else {
|
|
219
230
|
info(`File registry changed in ${CONFIG_PATH}.`);
|
|
220
231
|
}
|
|
@@ -227,6 +238,9 @@ async function cmdUpdate(): Promise<void> {
|
|
|
227
238
|
if (hasNewEntries && platformAdded) {
|
|
228
239
|
info(`Default platform config added to ${CONFIG_PATH}.`);
|
|
229
240
|
}
|
|
241
|
+
if (hasNewEntries && requiresPullRequestAdded) {
|
|
242
|
+
info(`Default requiresPullRequest=${defaults.requiresPullRequest} added to ${CONFIG_PATH}.`);
|
|
243
|
+
}
|
|
230
244
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
231
245
|
ok(`Updated ${CONFIG_PATH}`);
|
|
232
246
|
}
|
package/package.json
CHANGED
|
@@ -164,7 +164,7 @@ The receiving AI should read this document first to get up to speed.
|
|
|
164
164
|
|
|
165
165
|
### 1. One AI Per Phase
|
|
166
166
|
|
|
167
|
-
Don't have multiple AIs working on the same files simultaneously. Follow the sequential workflow:
|
|
167
|
+
Don't have multiple AIs working on the same files simultaneously. Follow the sequential workflow: analysis → analysis-review → design → design-review → code → code-review → commit (when any review finds issues, re-run the matching upstream stage).
|
|
168
168
|
|
|
169
169
|
### 2. Always Create Handoff Documents
|
|
170
170
|
|
|
@@ -164,7 +164,7 @@ cp .agents/templates/handoff.md .agents/workspace/active/handoff-task-001-phase2
|
|
|
164
164
|
|
|
165
165
|
### 1. 每个阶段一个 AI
|
|
166
166
|
|
|
167
|
-
不要让多个 AI
|
|
167
|
+
不要让多个 AI 同时处理相同的文件。遵循顺序工作流:分析 → 分析审查 → 设计 → 设计审查 → 编码 → 代码审查 → 提交(任一审查发现问题时,回到同名上游阶段重跑)。
|
|
168
168
|
|
|
169
169
|
### 2. 始终创建交接文档
|
|
170
170
|
|