@fitlab-ai/agent-infra 0.6.5 → 0.7.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 +51 -25
- package/README.zh-CN.md +49 -23
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/lib/builtin-tuis.js +45 -0
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/init.js +65 -23
- package/dist/lib/prompt.js +49 -1
- 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 +153 -0
- package/dist/lib/sandbox/commands/ls.js +24 -45
- package/dist/lib/sandbox/commands/rebuild.js +7 -13
- package/dist/lib/sandbox/commands/rm.js +2 -0
- package/dist/lib/sandbox/config.js +3 -0
- package/dist/lib/sandbox/image-prune.js +18 -0
- package/dist/lib/sandbox/index.js +2 -1
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/dist/lib/sandbox/task-resolver.js +18 -0
- package/dist/lib/sandbox/tools.js +213 -8
- package/dist/lib/update.js +70 -18
- package/lib/builtin-tuis.ts +55 -0
- package/lib/defaults.json +4 -0
- package/lib/init.ts +97 -35
- package/lib/prompt.ts +54 -1
- package/lib/sandbox/commands/create.ts +10 -2
- package/lib/sandbox/commands/enter.ts +14 -4
- package/lib/sandbox/commands/list-running.ts +188 -0
- package/lib/sandbox/commands/ls.ts +28 -49
- package/lib/sandbox/commands/rebuild.ts +12 -14
- package/lib/sandbox/commands/rm.ts +3 -0
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/image-prune.ts +23 -0
- package/lib/sandbox/index.ts +2 -1
- package/lib/sandbox/runtimes/ai-tools.dockerfile +10 -6
- package/lib/sandbox/task-resolver.ts +23 -1
- package/lib/sandbox/tools.ts +248 -9
- package/lib/update.ts +85 -30
- 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 +111 -2
- package/templates/.agents/README.zh-CN.md +111 -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/task-short-id.en.md +141 -0
- package/templates/.agents/rules/task-short-id.zh-CN.md +124 -0
- 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/task-short-id.js +713 -0
- package/templates/.agents/scripts/validate-artifact.js +1 -1
- package/templates/.agents/skills/analyze-task/SKILL.en.md +20 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +20 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +12 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +12 -1
- package/templates/.agents/skills/check-task/SKILL.en.md +47 -32
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +46 -32
- package/templates/.agents/skills/close-codescan/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +11 -0
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +11 -0
- package/templates/.agents/skills/code-task/SKILL.en.md +121 -0
- package/templates/.agents/skills/{implement-task → code-task}/SKILL.zh-CN.md +55 -25
- 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 +6 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -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 +17 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/create-pr/SKILL.en.md +21 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +21 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +14 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +15 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +15 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +16 -2
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +16 -2
- package/templates/.agents/skills/import-issue/SKILL.en.md +17 -3
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +17 -3
- package/templates/.agents/skills/plan-task/SKILL.en.md +8 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/restore-task/SKILL.en.md +16 -3
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +16 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +80 -0
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +105 -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 +15 -9
- package/templates/.agents/skills/{review-task → review-code}/SKILL.zh-CN.md +19 -10
- 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 +80 -0
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +105 -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/SKILL.en.md +1 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +113 -21
- package/templates/.agents/templates/task.en.md +4 -3
- package/templates/.agents/templates/task.zh-CN.md +3 -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
|
@@ -13,7 +13,7 @@ import { prepareDockerfile } from "../dockerfile.js";
|
|
|
13
13
|
import { detectEngine, ensureDocker } from "../engine.js";
|
|
14
14
|
import { commandForEngine, execEngine, run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine, runVerboseEngine } from "../shell.js";
|
|
15
15
|
import { resolveTaskBranch } from "../task-resolver.js";
|
|
16
|
-
import { resolveTools, toolConfigDirCandidates, toolNpmPackagesArg } from "../tools.js";
|
|
16
|
+
import { imageSignatureFields, resolveTools, toolConfigDirCandidates, toolNpmPackagesArg, toolShellInstallScriptBase64 } from "../tools.js";
|
|
17
17
|
import { hostJoin, toEnginePath, volumeArg } from "../engines/wsl2-paths.js";
|
|
18
18
|
import { clipboardHostDir, CONTAINER_CLIPBOARD_MOUNT } from "../clipboard/paths.js";
|
|
19
19
|
import { validateSelinuxDisableEnv } from "../engines/selinux.js";
|
|
@@ -58,7 +58,7 @@ function buildSignature(preparedDockerfile, tools) {
|
|
|
58
58
|
return createHash('sha256')
|
|
59
59
|
.update(JSON.stringify({
|
|
60
60
|
dockerfile: preparedDockerfile.signature,
|
|
61
|
-
tools: tools
|
|
61
|
+
tools: imageSignatureFields(tools)
|
|
62
62
|
}))
|
|
63
63
|
.digest('hex')
|
|
64
64
|
.slice(0, 12);
|
|
@@ -789,6 +789,8 @@ export function buildImage(config, tools, dockerfilePath, imageSignature, { engi
|
|
|
789
789
|
`HOST_GID=${hostGid}`,
|
|
790
790
|
'--build-arg',
|
|
791
791
|
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
792
|
+
'--build-arg',
|
|
793
|
+
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
792
794
|
'--label',
|
|
793
795
|
sandboxLabel(config),
|
|
794
796
|
'--label',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from "../config.js";
|
|
2
|
-
import { assertValidBranchName, containerNameCandidates } from "../constants.js";
|
|
2
|
+
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel } from "../constants.js";
|
|
3
3
|
import { detectEngine } from "../engine.js";
|
|
4
4
|
import { formatCredentialWarnings, formatRemaining, reconcileClaudeCredentials, redactCommandError, validateClaudeCredentialsEnvOverride } from "../credentials.js";
|
|
5
5
|
import { runInteractiveEngine, runSafeEngine } from "../shell.js";
|
|
@@ -7,7 +7,11 @@ import { resolveTaskBranch } from "../task-resolver.js";
|
|
|
7
7
|
import { dotfilesCacheDir, materializeDotfiles } from "../dotfiles.js";
|
|
8
8
|
import { runInteractiveWithClipboardBridge } from "../clipboard/bridge.js";
|
|
9
9
|
import { detectHostTimezone } from "../host-timezone.js";
|
|
10
|
-
|
|
10
|
+
import { fetchSandboxRows, isTaskShortRef, resolveTaskShortRef } from "./list-running.js";
|
|
11
|
+
const USAGE = `Usage: ai sandbox exec <branch | TASK-id | '#N'> [cmd...]
|
|
12
|
+
|
|
13
|
+
'#N' references the N-th running sandbox in 'ai sandbox ls' order (1-based).
|
|
14
|
+
Quote it as '#N' to avoid shell '#' comment handling.`;
|
|
11
15
|
const TMUX_ENTRY_PATH = '/usr/local/bin/sandbox-tmux-entry';
|
|
12
16
|
// Terminal-detection variables that interactive TUIs (e.g. claude-code)
|
|
13
17
|
// inspect to enable progressive enhancements such as the kitty keyboard
|
|
@@ -79,8 +83,15 @@ export async function enter(args) {
|
|
|
79
83
|
const config = loadConfig();
|
|
80
84
|
validateClaudeCredentialsEnvOverride();
|
|
81
85
|
const engine = detectEngine(config);
|
|
82
|
-
const [
|
|
83
|
-
|
|
86
|
+
const [firstArg = '', ...cmd] = args;
|
|
87
|
+
let branch;
|
|
88
|
+
if (isTaskShortRef(firstArg)) {
|
|
89
|
+
const { running } = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config));
|
|
90
|
+
branch = resolveTaskShortRef(firstArg, { running, repoRoot: config.repoRoot });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
branch = resolveTaskBranch(firstArg, config.repoRoot);
|
|
94
|
+
}
|
|
84
95
|
assertValidBranchName(branch);
|
|
85
96
|
const running = runSafeEngine(engine, 'docker', ['ps', '--format', '{{.Names}}']).split('\n');
|
|
86
97
|
const container = containerNameCandidates(config, branch).find((name) => running.includes(name));
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { runSafeEngine } from "../shell.js";
|
|
5
|
+
export function containerListFormat() {
|
|
6
|
+
return '{{.Names}}\t{{.Status}}\t{{.Labels}}';
|
|
7
|
+
}
|
|
8
|
+
export function parseLabels(csv) {
|
|
9
|
+
if (!csv) {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
const labels = {};
|
|
13
|
+
for (const pair of csv.split(',')) {
|
|
14
|
+
if (!pair) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const eq = pair.indexOf('=');
|
|
18
|
+
if (eq < 0) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
22
|
+
}
|
|
23
|
+
return labels;
|
|
24
|
+
}
|
|
25
|
+
export function parseSandboxRows(rawOutput, branchKey) {
|
|
26
|
+
if (!rawOutput) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return rawOutput.split('\n').map((line) => {
|
|
30
|
+
const [name = '', status = '', labelsCsv = ''] = line.split('\t');
|
|
31
|
+
const branch = parseLabels(labelsCsv)[branchKey] ?? '';
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
status,
|
|
35
|
+
branch,
|
|
36
|
+
running: status.startsWith('Up '),
|
|
37
|
+
index: null
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function sortAndIndexSandboxRows(rows) {
|
|
42
|
+
const byName = (a, b) => {
|
|
43
|
+
if (a.name < b.name)
|
|
44
|
+
return -1;
|
|
45
|
+
if (a.name > b.name)
|
|
46
|
+
return 1;
|
|
47
|
+
return 0;
|
|
48
|
+
};
|
|
49
|
+
const running = rows.filter((row) => row.running).sort(byName).map((row, i) => ({
|
|
50
|
+
...row,
|
|
51
|
+
index: i + 1
|
|
52
|
+
}));
|
|
53
|
+
const nonRunning = rows.filter((row) => !row.running).sort(byName).map((row) => ({
|
|
54
|
+
...row,
|
|
55
|
+
index: null
|
|
56
|
+
}));
|
|
57
|
+
return { running, nonRunning };
|
|
58
|
+
}
|
|
59
|
+
export function fetchSandboxRows(engine, label, branchKey) {
|
|
60
|
+
const raw = runSafeEngine(engine, 'docker', [
|
|
61
|
+
'ps',
|
|
62
|
+
'-a',
|
|
63
|
+
'--filter',
|
|
64
|
+
`label=${label}`,
|
|
65
|
+
'--format',
|
|
66
|
+
containerListFormat()
|
|
67
|
+
]);
|
|
68
|
+
return sortAndIndexSandboxRows(parseSandboxRows(raw, branchKey));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns true iff `arg` is a syntactically valid task short reference ('#N').
|
|
72
|
+
* Zero IO. Callers MUST use this as the gate before constructing any context
|
|
73
|
+
* for resolveTaskShortRef — that way non-matching arguments (e.g. '#abc',
|
|
74
|
+
* '#1.5', '#') never trigger sandbox list IO.
|
|
75
|
+
*/
|
|
76
|
+
export function isTaskShortRef(arg) {
|
|
77
|
+
return /^#\d+$/.test(arg);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Try to resolve a short ref against the global task-short-id registry.
|
|
81
|
+
*
|
|
82
|
+
* Tri-state semantics (review-code Round 1 M-1 fix):
|
|
83
|
+
* - 'miss' → script reports no entry (or registry script missing). Caller may fall back.
|
|
84
|
+
* - 'hit' → registry resolved to a task id and branch is found in task.md.
|
|
85
|
+
* - throws → registry hit but task.md is missing or branch metadata is unparseable;
|
|
86
|
+
* surfacing this error is critical — never silently fall back to running index.
|
|
87
|
+
*/
|
|
88
|
+
function tryResolveFromRegistry(arg, repoRoot) {
|
|
89
|
+
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
90
|
+
if (!fs.existsSync(scriptPath))
|
|
91
|
+
return { status: 'miss' };
|
|
92
|
+
const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
|
|
93
|
+
if (result.status !== 0)
|
|
94
|
+
return { status: 'miss' };
|
|
95
|
+
const taskId = (result.stdout || '').trim();
|
|
96
|
+
if (!/^TASK-\d{8}-\d{6}$/.test(taskId)) {
|
|
97
|
+
throw new Error(`Registry returned malformed task id for '${arg}': ${JSON.stringify(taskId)}`);
|
|
98
|
+
}
|
|
99
|
+
for (const sub of ['active', 'completed', 'blocked', 'archive']) {
|
|
100
|
+
const taskMdPath = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
|
|
101
|
+
if (!fs.existsSync(taskMdPath))
|
|
102
|
+
continue;
|
|
103
|
+
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
104
|
+
const fm = content.match(/^branch:\s*(.+)$/m);
|
|
105
|
+
if (fm?.[1]?.trim()) {
|
|
106
|
+
return { status: 'hit', branch: fm[1].trim().replace(/^(["'])(.*)\1$/, '$2') };
|
|
107
|
+
}
|
|
108
|
+
const ctx = content.match(/^- \*\*(?:分支|Branch)\*\*:[ \t]*`?([^`\n]+)`?$/m);
|
|
109
|
+
if (ctx?.[1]?.trim()) {
|
|
110
|
+
return { status: 'hit', branch: ctx[1].trim().replace(/^(["'])(.*)\1$/, '$2') };
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`Short ref '${arg}' resolved to task ${taskId} but task.md has no branch field`);
|
|
113
|
+
}
|
|
114
|
+
throw new Error(`Short ref '${arg}' resolved to task ${taskId} but task.md was not found under any workspace dir`);
|
|
115
|
+
}
|
|
116
|
+
function resolveByRunningIndex(arg, running) {
|
|
117
|
+
const n = Number(arg.slice(1));
|
|
118
|
+
if (n < 1) {
|
|
119
|
+
throw new Error(`Invalid sandbox index '${arg}': must be >= 1`);
|
|
120
|
+
}
|
|
121
|
+
if (running.length === 0) {
|
|
122
|
+
throw new Error(`No running sandbox to reference with '${arg}'`);
|
|
123
|
+
}
|
|
124
|
+
if (n > running.length) {
|
|
125
|
+
throw new Error(`No running sandbox at index '${arg}' (only ${running.length} running)`);
|
|
126
|
+
}
|
|
127
|
+
const row = running[n - 1];
|
|
128
|
+
if (!row.branch) {
|
|
129
|
+
throw new Error(`Cannot resolve branch for sandbox '${arg}' (container '${row.name}' missing branch label)`);
|
|
130
|
+
}
|
|
131
|
+
return row.branch;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve a task short reference ('#N') to a branch name for the sandbox entrypoint.
|
|
135
|
+
*
|
|
136
|
+
* Resolution order (sandbox fallback mode, plan-r7 C2):
|
|
137
|
+
* 1. Try the global task-short-id registry under repoRoot. If hit, look up the
|
|
138
|
+
* branch from the matching task.md.
|
|
139
|
+
* 2. Fallback to the running-sandbox list index (preserves the #414 ls-index
|
|
140
|
+
* behaviour; long-term contract per analysis-r5).
|
|
141
|
+
*
|
|
142
|
+
* Precondition: callers MUST gate on isTaskShortRef(arg) === true.
|
|
143
|
+
*/
|
|
144
|
+
export function resolveTaskShortRef(arg, ctx) {
|
|
145
|
+
if (ctx.repoRoot) {
|
|
146
|
+
const lookup = tryResolveFromRegistry(arg, ctx.repoRoot);
|
|
147
|
+
if (lookup.status === 'hit')
|
|
148
|
+
return lookup.branch;
|
|
149
|
+
// 'miss' falls through to ls-index fallback (preserves #414 behaviour); 'hit-but-invalid' already threw above.
|
|
150
|
+
}
|
|
151
|
+
return resolveByRunningIndex(arg, ctx.running);
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=list-running.js.map
|
|
@@ -5,39 +5,24 @@ import pc from 'picocolors';
|
|
|
5
5
|
import { loadConfig } from "../config.js";
|
|
6
6
|
import { sandboxBranchLabel, sandboxLabel } from "../constants.js";
|
|
7
7
|
import { detectEngine } from "../engine.js";
|
|
8
|
-
import { runSafeEngine } from "../shell.js";
|
|
9
8
|
import { resolveTools, toolProjectDirCandidates } from "../tools.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
const labels = {};
|
|
21
|
-
for (const pair of csv.split(',')) {
|
|
22
|
-
if (!pair) {
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
const eq = pair.indexOf('=');
|
|
26
|
-
if (eq < 0) {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
30
|
-
}
|
|
31
|
-
return labels;
|
|
32
|
-
}
|
|
9
|
+
import { fetchSandboxRows } from "./list-running.js";
|
|
10
|
+
export { containerListFormat, parseLabels } from "./list-running.js";
|
|
11
|
+
const USAGE = `Usage: ai sandbox ls
|
|
12
|
+
|
|
13
|
+
Lists all containers for the current project. The leftmost '#' column
|
|
14
|
+
numbers running sandboxes; use it as "ai sandbox exec '#N'" to enter one.
|
|
15
|
+
Quote '#N' to avoid shell '#' comment handling.`;
|
|
16
|
+
const CONTAINER_TABLE_HEADERS = ['#', 'NAMES', 'STATUS', 'BRANCH'];
|
|
33
17
|
export function formatContainerTable(rows) {
|
|
34
|
-
const columns = rows.map((row) => [row.name, row.status, row.branch]);
|
|
18
|
+
const columns = rows.map((row) => [row.index, row.name, row.status, row.branch]);
|
|
35
19
|
const widths = [
|
|
36
|
-
Math.max(CONTAINER_TABLE_HEADERS[0].length, ...rows.map((row) => row.
|
|
37
|
-
Math.max(CONTAINER_TABLE_HEADERS[1].length, ...rows.map((row) => row.
|
|
38
|
-
Math.max(CONTAINER_TABLE_HEADERS[2].length, ...rows.map((row) => row.
|
|
20
|
+
Math.max(CONTAINER_TABLE_HEADERS[0].length, ...rows.map((row) => row.index.length)),
|
|
21
|
+
Math.max(CONTAINER_TABLE_HEADERS[1].length, ...rows.map((row) => row.name.length)),
|
|
22
|
+
Math.max(CONTAINER_TABLE_HEADERS[2].length, ...rows.map((row) => row.status.length)),
|
|
23
|
+
Math.max(CONTAINER_TABLE_HEADERS[3].length, ...rows.map((row) => row.branch.length))
|
|
39
24
|
];
|
|
40
|
-
const renderRow = (values) => `${values[0].padEnd(widths[0])} ${values[1].padEnd(widths[1])} ${values[2]}`.trimEnd();
|
|
25
|
+
const renderRow = (values) => `${values[0].padEnd(widths[0])} ${values[1].padEnd(widths[1])} ${values[2].padEnd(widths[2])} ${values[3]}`.trimEnd();
|
|
41
26
|
return [
|
|
42
27
|
renderRow(CONTAINER_TABLE_HEADERS),
|
|
43
28
|
...columns.map((column) => renderRow(column))
|
|
@@ -58,27 +43,21 @@ export function ls(args = []) {
|
|
|
58
43
|
const engine = detectEngine(config);
|
|
59
44
|
const tools = resolveTools(config);
|
|
60
45
|
const label = sandboxLabel(config);
|
|
61
|
-
const
|
|
62
|
-
'ps',
|
|
63
|
-
'-a',
|
|
64
|
-
'--filter',
|
|
65
|
-
`label=${label}`,
|
|
66
|
-
'--format',
|
|
67
|
-
containerListFormat()
|
|
68
|
-
]);
|
|
46
|
+
const { running, nonRunning } = fetchSandboxRows(engine, label, sandboxBranchLabel(config));
|
|
69
47
|
p.intro(pc.cyan(`Sandbox status for ${config.project}`));
|
|
70
48
|
p.log.step('Containers');
|
|
71
|
-
|
|
49
|
+
const ordered = [...running, ...nonRunning];
|
|
50
|
+
if (ordered.length === 0) {
|
|
72
51
|
p.log.warn(' No sandbox containers');
|
|
73
52
|
}
|
|
74
53
|
else {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
81
|
-
for (const line of formatContainerTable(
|
|
54
|
+
const tableRows = ordered.map((row) => ({
|
|
55
|
+
index: row.index === null ? '' : String(row.index),
|
|
56
|
+
name: row.name,
|
|
57
|
+
status: row.status,
|
|
58
|
+
branch: row.branch
|
|
59
|
+
}));
|
|
60
|
+
for (const line of formatContainerTable(tableRows)) {
|
|
82
61
|
process.stdout.write(` ${line}\n`);
|
|
83
62
|
}
|
|
84
63
|
}
|
|
@@ -6,8 +6,9 @@ import { loadConfig } from "../config.js";
|
|
|
6
6
|
import { prepareDockerfile } from "../dockerfile.js";
|
|
7
7
|
import { sandboxImageConfigLabel, sandboxLabel } from "../constants.js";
|
|
8
8
|
import { detectEngine, ensureDocker } from "../engine.js";
|
|
9
|
-
import { runEngine,
|
|
10
|
-
import {
|
|
9
|
+
import { runEngine, runSafeEngine, runVerboseEngine } from "../shell.js";
|
|
10
|
+
import { pruneSandboxDanglingImages } from "../image-prune.js";
|
|
11
|
+
import { imageSignatureFields, resolveTools, toolNpmPackagesArg, toolShellInstallScriptBase64 } from "../tools.js";
|
|
11
12
|
import { toEnginePath } from "../engines/wsl2-paths.js";
|
|
12
13
|
import { resolveBuildUid } from "../engines/native.js";
|
|
13
14
|
const USAGE = `Usage: ai sandbox rebuild [--quiet] [--refresh]`;
|
|
@@ -15,7 +16,7 @@ function buildSignature(preparedDockerfile, tools) {
|
|
|
15
16
|
return createHash('sha256')
|
|
16
17
|
.update(JSON.stringify({
|
|
17
18
|
dockerfile: preparedDockerfile.signature,
|
|
18
|
-
tools: tools
|
|
19
|
+
tools: imageSignatureFields(tools)
|
|
19
20
|
}))
|
|
20
21
|
.digest('hex')
|
|
21
22
|
.slice(0, 12);
|
|
@@ -38,6 +39,8 @@ export function buildArgs(config, tools, dockerfilePath, imageSignature, { engin
|
|
|
38
39
|
`HOST_GID=${hostGid}`,
|
|
39
40
|
'--build-arg',
|
|
40
41
|
`AI_TOOL_PACKAGES=${toolNpmPackagesArg(tools)}`,
|
|
42
|
+
'--build-arg',
|
|
43
|
+
`AI_TOOLS_SHELL_INSTALL_B64=${toolShellInstallScriptBase64(tools)}`,
|
|
41
44
|
'--label',
|
|
42
45
|
sandboxLabel(config),
|
|
43
46
|
'--label',
|
|
@@ -51,11 +54,6 @@ export function buildArgs(config, tools, dockerfilePath, imageSignature, { engin
|
|
|
51
54
|
}
|
|
52
55
|
return args;
|
|
53
56
|
}
|
|
54
|
-
function removeImageIfPresent(imageName, engine) {
|
|
55
|
-
if (runOkEngine(engine, 'docker', ['image', 'inspect', imageName])) {
|
|
56
|
-
runEngine(engine, 'docker', ['rmi', imageName]);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
57
|
export async function rebuild(args) {
|
|
60
58
|
const { values } = parseArgs({
|
|
61
59
|
args,
|
|
@@ -83,9 +81,6 @@ export async function rebuild(args) {
|
|
|
83
81
|
try {
|
|
84
82
|
if (quiet) {
|
|
85
83
|
const spinner = p.spinner();
|
|
86
|
-
spinner.start(`Removing old image ${config.imageName}...`);
|
|
87
|
-
removeImageIfPresent(config.imageName, engine);
|
|
88
|
-
spinner.stop('Old image removed');
|
|
89
84
|
spinner.start('Building image...');
|
|
90
85
|
runEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, { engine, refresh }), {
|
|
91
86
|
cwd: config.repoRoot
|
|
@@ -93,12 +88,11 @@ export async function rebuild(args) {
|
|
|
93
88
|
spinner.stop(pc.green('Sandbox image rebuilt'));
|
|
94
89
|
}
|
|
95
90
|
else {
|
|
96
|
-
p.log.step(`Removing old image ${config.imageName}`);
|
|
97
|
-
removeImageIfPresent(config.imageName, engine);
|
|
98
91
|
p.log.step('Building image');
|
|
99
92
|
runVerboseEngine(engine, 'docker', buildArgs(config, tools, preparedDockerfile.path, imageSignature, { engine, refresh }), { cwd: config.repoRoot });
|
|
100
93
|
p.log.success(pc.green('Sandbox image rebuilt'));
|
|
101
94
|
}
|
|
95
|
+
pruneSandboxDanglingImages(config, engine);
|
|
102
96
|
}
|
|
103
97
|
finally {
|
|
104
98
|
preparedDockerfile.cleanup();
|
|
@@ -6,6 +6,7 @@ import pc from 'picocolors';
|
|
|
6
6
|
import { loadConfig } from "../config.js";
|
|
7
7
|
import { assertValidBranchName, containerNameCandidates, sandboxBranchLabel, sandboxLabel, shareBranchDir, shellConfigDirCandidates, worktreeDirCandidates } from "../constants.js";
|
|
8
8
|
import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedVm } from "../engine.js";
|
|
9
|
+
import { pruneSandboxDanglingImages } from "../image-prune.js";
|
|
9
10
|
import { removeManagedDir, removeWorktreeDir } from "../managed-fs.js";
|
|
10
11
|
import { runOk, runSafe, runSafeEngine } from "../shell.js";
|
|
11
12
|
import { resolveTaskBranch } from "../task-resolver.js";
|
|
@@ -174,6 +175,7 @@ async function rmAll(config, tools) {
|
|
|
174
175
|
if (!p.isCancel(shouldRemoveImage) && shouldRemoveImage) {
|
|
175
176
|
runSafeEngine(engine, 'docker', ['rmi', config.imageName]);
|
|
176
177
|
}
|
|
178
|
+
pruneSandboxDanglingImages(config, engine);
|
|
177
179
|
if (isManagedEngine(engine)) {
|
|
178
180
|
if (engine === ENGINES.WSL2) {
|
|
179
181
|
p.log.warn('Windows uses Docker Desktop with WSL2. Stop it from Docker Desktop or run "wsl --shutdown" manually.');
|
|
@@ -6,6 +6,7 @@ import pc from 'picocolors';
|
|
|
6
6
|
import { validateSandboxEngine } from "./engine.js";
|
|
7
7
|
import { hostJoin } from "./engines/wsl2-paths.js";
|
|
8
8
|
import { findRuntimeEngineMismatches } from "./runtime-engines.js";
|
|
9
|
+
import { parseCustomTools } from "./tools.js";
|
|
9
10
|
const DEFAULTS = Object.freeze({
|
|
10
11
|
engine: null,
|
|
11
12
|
runtimes: ['node22'],
|
|
@@ -76,6 +77,7 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
76
77
|
' Update "sandbox.runtimes" in .agents/.airc.json (e.g. "node22"), or relax "engines.node".\n'));
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
const customTools = parseCustomTools(sandbox.customTools, { home });
|
|
79
81
|
return {
|
|
80
82
|
repoRoot,
|
|
81
83
|
configPath,
|
|
@@ -93,6 +95,7 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
93
95
|
tools: Array.isArray(sandbox.tools) && sandbox.tools.length > 0
|
|
94
96
|
? [...sandbox.tools]
|
|
95
97
|
: defaults.tools,
|
|
98
|
+
customTools,
|
|
96
99
|
dockerfile,
|
|
97
100
|
vm: {
|
|
98
101
|
cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import { sandboxLabel } from "./constants.js";
|
|
3
|
+
import { runEngine } from "./shell.js";
|
|
4
|
+
export function pruneSandboxDanglingImages(config, engine) {
|
|
5
|
+
try {
|
|
6
|
+
runEngine(engine, 'docker', [
|
|
7
|
+
'image',
|
|
8
|
+
'prune',
|
|
9
|
+
'-f',
|
|
10
|
+
'--filter',
|
|
11
|
+
`label=${sandboxLabel(config)}`
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
p.log.warn(`Failed to prune dangling sandbox images (label=${sandboxLabel(config)}); leaving them in place.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=image-prune.js.map
|
|
@@ -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
|
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
5
|
+
const SHORT_ID_RE = /^#\d+$/;
|
|
4
6
|
const WORKSPACE_DIRS = ['active', 'completed', 'blocked', 'archive'];
|
|
7
|
+
function resolveShortIdStrict(arg, repoRoot) {
|
|
8
|
+
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
9
|
+
if (!fs.existsSync(scriptPath)) {
|
|
10
|
+
throw new Error(`Short id '${arg}' provided but task-short-id.js script is missing at ${scriptPath}`);
|
|
11
|
+
}
|
|
12
|
+
const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
|
|
13
|
+
if (result.status !== 0) {
|
|
14
|
+
throw new Error(`Short id '${arg}' not found in active task registry: ${(result.stderr || '').trim()}`);
|
|
15
|
+
}
|
|
16
|
+
return result.stdout.trim();
|
|
17
|
+
}
|
|
5
18
|
function stripQuotes(value) {
|
|
6
19
|
return value.replace(/^(["'])(.*)\1$/, '$2');
|
|
7
20
|
}
|
|
@@ -26,6 +39,11 @@ function resolveBranchFromTaskContent(content, taskId) {
|
|
|
26
39
|
throw new Error(`Task ${taskId} has no branch field in task.md`);
|
|
27
40
|
}
|
|
28
41
|
export function resolveTaskBranch(arg, repoRoot) {
|
|
42
|
+
if (SHORT_ID_RE.test(arg)) {
|
|
43
|
+
const taskId = resolveShortIdStrict(arg, repoRoot);
|
|
44
|
+
const content = readTaskContent(repoRoot, taskId);
|
|
45
|
+
return resolveBranchFromTaskContent(content, taskId);
|
|
46
|
+
}
|
|
29
47
|
if (!TASK_ID_RE.test(arg)) {
|
|
30
48
|
return arg;
|
|
31
49
|
}
|