@vemdev/cli 0.1.53 → 0.1.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +377 -165
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -891,7 +891,13 @@ function truncateForDisplay(value, maxChars) {
|
|
|
891
891
|
return `${trimmed.slice(0, Math.max(0, maxChars - 15)).trimEnd()}
|
|
892
892
|
...[truncated]`;
|
|
893
893
|
}
|
|
894
|
-
var AGENT_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
894
|
+
var AGENT_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
895
|
+
"todo",
|
|
896
|
+
"in-review",
|
|
897
|
+
"in-progress",
|
|
898
|
+
"blocked",
|
|
899
|
+
"done"
|
|
900
|
+
]);
|
|
895
901
|
var MAX_CHILD_TASKS_IN_PROMPT = 12;
|
|
896
902
|
var TASK_STATUS_ORDER = {
|
|
897
903
|
"in-review": 0,
|
|
@@ -1102,24 +1108,33 @@ var updateTaskMetaRemote = async (configService, task, patch) => {
|
|
|
1102
1108
|
payload.actor = patch.actor.trim().length > 0 ? patch.actor.trim() : void 0;
|
|
1103
1109
|
}
|
|
1104
1110
|
if (patch.title !== void 0) payload.title = patch.title;
|
|
1105
|
-
if (patch.description !== void 0)
|
|
1111
|
+
if (patch.description !== void 0)
|
|
1112
|
+
payload.description = patch.description;
|
|
1106
1113
|
if (patch.priority !== void 0) payload.priority = patch.priority;
|
|
1107
1114
|
if (patch.tags !== void 0) payload.tags = patch.tags;
|
|
1108
1115
|
if (patch.type !== void 0) payload.type = patch.type;
|
|
1109
|
-
if (patch.estimate_hours !== void 0)
|
|
1116
|
+
if (patch.estimate_hours !== void 0)
|
|
1117
|
+
payload.estimate_hours = patch.estimate_hours;
|
|
1110
1118
|
if (patch.depends_on !== void 0) payload.depends_on = patch.depends_on;
|
|
1111
1119
|
if (patch.blocked_by !== void 0) payload.blocked_by = patch.blocked_by;
|
|
1112
|
-
if (patch.recurrence_rule !== void 0)
|
|
1120
|
+
if (patch.recurrence_rule !== void 0)
|
|
1121
|
+
payload.recurrence_rule = patch.recurrence_rule;
|
|
1113
1122
|
if (patch.owner_id !== void 0) payload.owner_id = patch.owner_id;
|
|
1114
|
-
if (patch.reviewer_id !== void 0)
|
|
1115
|
-
|
|
1123
|
+
if (patch.reviewer_id !== void 0)
|
|
1124
|
+
payload.reviewer_id = patch.reviewer_id;
|
|
1125
|
+
if (patch.validation_steps !== void 0)
|
|
1126
|
+
payload.validation_steps = patch.validation_steps;
|
|
1116
1127
|
if (patch.user_notes !== void 0) payload.user_notes = patch.user_notes;
|
|
1117
|
-
if (patch.github_issue_number !== void 0)
|
|
1128
|
+
if (patch.github_issue_number !== void 0)
|
|
1129
|
+
payload.github_issue_number = patch.github_issue_number;
|
|
1118
1130
|
if (patch.parent_id !== void 0) payload.parent_id = patch.parent_id;
|
|
1119
|
-
if (patch.subtask_order !== void 0)
|
|
1131
|
+
if (patch.subtask_order !== void 0)
|
|
1132
|
+
payload.subtask_order = patch.subtask_order;
|
|
1120
1133
|
if (patch.due_at !== void 0) payload.due_at = patch.due_at;
|
|
1121
|
-
if (patch.raw_vem_update !== void 0)
|
|
1122
|
-
|
|
1134
|
+
if (patch.raw_vem_update !== void 0)
|
|
1135
|
+
payload.raw_vem_update = patch.raw_vem_update;
|
|
1136
|
+
if (patch.cli_version !== void 0)
|
|
1137
|
+
payload.cli_version = patch.cli_version;
|
|
1123
1138
|
const response = await fetch(
|
|
1124
1139
|
`${API_URL}/tasks/${encodeURIComponent(dbId)}/meta`,
|
|
1125
1140
|
{
|
|
@@ -1237,7 +1252,7 @@ var syncParsedTaskUpdatesToRemote = async (configService, update, result) => {
|
|
|
1237
1252
|
...patch.subtask_order !== void 0 ? { subtask_order: patch.subtask_order } : {},
|
|
1238
1253
|
...patch.due_at !== void 0 ? { due_at: patch.due_at } : {},
|
|
1239
1254
|
raw_vem_update: JSON.parse(JSON.stringify(update)),
|
|
1240
|
-
cli_version: "0.1.
|
|
1255
|
+
cli_version: "0.1.54"
|
|
1241
1256
|
});
|
|
1242
1257
|
const taskContextPatch = buildRemoteTaskContextPatch(patch, updatedTask);
|
|
1243
1258
|
if (taskContextPatch) {
|
|
@@ -1843,7 +1858,11 @@ Your task is ${activeTask?.id}: ${activeTask?.title}${childScopeText}.
|
|
|
1843
1858
|
|
|
1844
1859
|
Start by reading .vem/task_context.md and .vem/current_context.md for task and project context. Then explore the repository structure (list directories, read key files like package.json, README, and relevant source files) to understand the codebase before writing any code. Implement all required changes, run any existing tests or builds to verify, then provide the vem_update block.`;
|
|
1845
1860
|
if (options.autoExit) {
|
|
1846
|
-
console.log(
|
|
1861
|
+
console.log(
|
|
1862
|
+
chalk7.cyan(
|
|
1863
|
+
"Auto-injecting context via -p flag (autonomous mode)..."
|
|
1864
|
+
)
|
|
1865
|
+
);
|
|
1847
1866
|
launchArgs = [...launchArgs, "-p", autonomousPrompt, "--yolo"];
|
|
1848
1867
|
} else {
|
|
1849
1868
|
console.log(chalk7.cyan("Auto-injecting context via -i flag..."));
|
|
@@ -2510,7 +2529,11 @@ function registerCycleCommands(program2) {
|
|
|
2510
2529
|
try {
|
|
2511
2530
|
const cycles = await cycleService.getCycles();
|
|
2512
2531
|
if (cycles.length === 0) {
|
|
2513
|
-
console.log(
|
|
2532
|
+
console.log(
|
|
2533
|
+
chalk9.gray(
|
|
2534
|
+
"\n No cycles yet. Create one with: vem cycle create\n"
|
|
2535
|
+
)
|
|
2536
|
+
);
|
|
2514
2537
|
return;
|
|
2515
2538
|
}
|
|
2516
2539
|
const table = new Table({
|
|
@@ -2634,7 +2657,9 @@ function registerCycleCommands(program2) {
|
|
|
2634
2657
|
process.exitCode = 1;
|
|
2635
2658
|
return;
|
|
2636
2659
|
}
|
|
2637
|
-
const updated = await cycleService.updateCycle(id, {
|
|
2660
|
+
const updated = await cycleService.updateCycle(id, {
|
|
2661
|
+
status: "active"
|
|
2662
|
+
});
|
|
2638
2663
|
console.log(chalk9.cyan(`
|
|
2639
2664
|
\u2714 Cycle ${id} is now active
|
|
2640
2665
|
`));
|
|
@@ -2662,7 +2687,9 @@ function registerCycleCommands(program2) {
|
|
|
2662
2687
|
`));
|
|
2663
2688
|
return;
|
|
2664
2689
|
}
|
|
2665
|
-
const updated = await cycleService.updateCycle(id, {
|
|
2690
|
+
const updated = await cycleService.updateCycle(id, {
|
|
2691
|
+
status: "closed"
|
|
2692
|
+
});
|
|
2666
2693
|
console.log(chalk9.green(`
|
|
2667
2694
|
\u2714 Cycle ${id} closed
|
|
2668
2695
|
`));
|
|
@@ -2678,8 +2705,10 @@ function registerCycleCommands(program2) {
|
|
|
2678
2705
|
);
|
|
2679
2706
|
}
|
|
2680
2707
|
console.log(
|
|
2681
|
-
chalk9.gray(
|
|
2682
|
-
`)
|
|
2708
|
+
chalk9.gray(
|
|
2709
|
+
` Closed: ${new Date(updated.closed_at).toLocaleDateString()}
|
|
2710
|
+
`
|
|
2711
|
+
)
|
|
2683
2712
|
);
|
|
2684
2713
|
} catch (error) {
|
|
2685
2714
|
console.error(chalk9.red(`Failed to close cycle: ${error.message}`));
|
|
@@ -2787,13 +2816,17 @@ function registerCycleCommands(program2) {
|
|
|
2787
2816
|
]);
|
|
2788
2817
|
}
|
|
2789
2818
|
const done = cycleTasks.filter((t) => t.status === "done").length;
|
|
2790
|
-
console.log(
|
|
2819
|
+
console.log(
|
|
2820
|
+
`
|
|
2791
2821
|
${chalk9.white(String(done))}/${chalk9.white(String(cycleTasks.length))} tasks done
|
|
2792
|
-
`
|
|
2822
|
+
`
|
|
2823
|
+
);
|
|
2793
2824
|
console.log(table.toString());
|
|
2794
2825
|
console.log();
|
|
2795
2826
|
} catch (error) {
|
|
2796
|
-
console.error(
|
|
2827
|
+
console.error(
|
|
2828
|
+
chalk9.red(`Failed to show cycle focus: ${error.message}`)
|
|
2829
|
+
);
|
|
2797
2830
|
}
|
|
2798
2831
|
});
|
|
2799
2832
|
}
|
|
@@ -2806,7 +2839,9 @@ import prompts5 from "prompts";
|
|
|
2806
2839
|
async function getRepoRoot2() {
|
|
2807
2840
|
const { execSync: execSync4 } = await import("child_process");
|
|
2808
2841
|
try {
|
|
2809
|
-
return execSync4("git rev-parse --show-toplevel", {
|
|
2842
|
+
return execSync4("git rev-parse --show-toplevel", {
|
|
2843
|
+
encoding: "utf-8"
|
|
2844
|
+
}).trim();
|
|
2810
2845
|
} catch {
|
|
2811
2846
|
return process.cwd();
|
|
2812
2847
|
}
|
|
@@ -2877,9 +2912,7 @@ function registerInstructionCommands(program2) {
|
|
|
2877
2912
|
const dest = path.resolve(repoRoot, entry.path);
|
|
2878
2913
|
const resolvedRoot = path.resolve(repoRoot);
|
|
2879
2914
|
if (!dest.startsWith(`${resolvedRoot}${path.sep}`) && dest !== resolvedRoot) {
|
|
2880
|
-
console.warn(
|
|
2881
|
-
chalk10.yellow(`Skipping unsafe path: ${entry.path}`)
|
|
2882
|
-
);
|
|
2915
|
+
console.warn(chalk10.yellow(`Skipping unsafe path: ${entry.path}`));
|
|
2883
2916
|
continue;
|
|
2884
2917
|
}
|
|
2885
2918
|
if (!options.force) {
|
|
@@ -2905,9 +2938,11 @@ function registerInstructionCommands(program2) {
|
|
|
2905
2938
|
}
|
|
2906
2939
|
const skippedMsg = skipped > 0 ? `, ${skipped} skipped` : "";
|
|
2907
2940
|
console.log(
|
|
2908
|
-
chalk10.green(
|
|
2941
|
+
chalk10.green(
|
|
2942
|
+
`
|
|
2909
2943
|
\u2714 Pulled ${written} instruction file(s)${skippedMsg}.
|
|
2910
|
-
`
|
|
2944
|
+
`
|
|
2945
|
+
)
|
|
2911
2946
|
);
|
|
2912
2947
|
} catch (error) {
|
|
2913
2948
|
console.error(
|
|
@@ -2935,9 +2970,7 @@ function registerInstructionCommands(program2) {
|
|
|
2935
2970
|
const localInstructions = await readLocalInstructions();
|
|
2936
2971
|
if (localInstructions.length === 0) {
|
|
2937
2972
|
console.log(
|
|
2938
|
-
chalk10.yellow(
|
|
2939
|
-
"No instruction files found locally. Looked for:"
|
|
2940
|
-
)
|
|
2973
|
+
chalk10.yellow("No instruction files found locally. Looked for:")
|
|
2941
2974
|
);
|
|
2942
2975
|
for (const f of KNOWN_AGENT_INSTRUCTION_FILES) {
|
|
2943
2976
|
console.log(chalk10.gray(` ${f}`));
|
|
@@ -2975,11 +3008,9 @@ function registerInstructionCommands(program2) {
|
|
|
2975
3008
|
console.log(chalk10.green(` \u2714 ${entry.path}`));
|
|
2976
3009
|
}
|
|
2977
3010
|
const versionNote = data.version_number ? ` (saved as v${data.version_number})` : "";
|
|
2978
|
-
console.log(
|
|
2979
|
-
chalk10.green(`
|
|
3011
|
+
console.log(chalk10.green(`
|
|
2980
3012
|
\u2714 Instructions pushed${versionNote}.
|
|
2981
|
-
`)
|
|
2982
|
-
);
|
|
3013
|
+
`));
|
|
2983
3014
|
} catch (error) {
|
|
2984
3015
|
console.error(
|
|
2985
3016
|
chalk10.red("\n\u2716 Instructions push failed:"),
|
|
@@ -2988,9 +3019,7 @@ function registerInstructionCommands(program2) {
|
|
|
2988
3019
|
process.exitCode = 1;
|
|
2989
3020
|
}
|
|
2990
3021
|
});
|
|
2991
|
-
instructionsCmd.command("status").description(
|
|
2992
|
-
"Check if local instruction files are in sync with the cloud"
|
|
2993
|
-
).action(async () => {
|
|
3022
|
+
instructionsCmd.command("status").description("Check if local instruction files are in sync with the cloud").action(async () => {
|
|
2994
3023
|
await trackCommandUsage("instructions.status");
|
|
2995
3024
|
try {
|
|
2996
3025
|
const configService = new ConfigService();
|
|
@@ -3022,8 +3051,12 @@ function registerInstructionCommands(program2) {
|
|
|
3022
3051
|
}
|
|
3023
3052
|
const cloudData = await cloudRes.json();
|
|
3024
3053
|
const cloudInstructions = cloudData.instructions ?? [];
|
|
3025
|
-
const localMap = new Map(
|
|
3026
|
-
|
|
3054
|
+
const localMap = new Map(
|
|
3055
|
+
localInstructions.map((e) => [e.path, e.content])
|
|
3056
|
+
);
|
|
3057
|
+
const cloudMap = new Map(
|
|
3058
|
+
cloudInstructions.map((e) => [e.path, e.content])
|
|
3059
|
+
);
|
|
3027
3060
|
const allPaths = /* @__PURE__ */ new Set([...localMap.keys(), ...cloudMap.keys()]);
|
|
3028
3061
|
let inSync = true;
|
|
3029
3062
|
console.log(chalk10.bold("\nInstruction file sync status:\n"));
|
|
@@ -3046,7 +3079,9 @@ function registerInstructionCommands(program2) {
|
|
|
3046
3079
|
);
|
|
3047
3080
|
inSync = false;
|
|
3048
3081
|
} else {
|
|
3049
|
-
console.log(
|
|
3082
|
+
console.log(
|
|
3083
|
+
chalk10.green(` \u2714 ${filePath}`) + chalk10.gray(" (in sync)")
|
|
3084
|
+
);
|
|
3050
3085
|
}
|
|
3051
3086
|
}
|
|
3052
3087
|
if (allPaths.size === 0) {
|
|
@@ -3165,9 +3200,7 @@ function registerInstructionCommands(program2) {
|
|
|
3165
3200
|
)
|
|
3166
3201
|
);
|
|
3167
3202
|
console.log(
|
|
3168
|
-
chalk10.gray(
|
|
3169
|
-
" Run `vem instructions pull` to update local files."
|
|
3170
|
-
)
|
|
3203
|
+
chalk10.gray(" Run `vem instructions pull` to update local files.")
|
|
3171
3204
|
);
|
|
3172
3205
|
} catch (error) {
|
|
3173
3206
|
console.error(
|
|
@@ -4109,7 +4142,13 @@ function commandExists(command) {
|
|
|
4109
4142
|
return false;
|
|
4110
4143
|
}
|
|
4111
4144
|
}
|
|
4112
|
-
var KNOWN_RUNNER_AGENTS = [
|
|
4145
|
+
var KNOWN_RUNNER_AGENTS = [
|
|
4146
|
+
"copilot",
|
|
4147
|
+
"gh",
|
|
4148
|
+
"claude",
|
|
4149
|
+
"gemini",
|
|
4150
|
+
"codex"
|
|
4151
|
+
];
|
|
4113
4152
|
function hasSandboxCredentials(agent) {
|
|
4114
4153
|
if (agent === "claude") {
|
|
4115
4154
|
return typeof process.env.ANTHROPIC_API_KEY === "string" && process.env.ANTHROPIC_API_KEY.trim().length > 0;
|
|
@@ -4118,7 +4157,9 @@ function hasSandboxCredentials(agent) {
|
|
|
4118
4157
|
const envToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
4119
4158
|
if (envToken && envToken.trim().length > 0) return true;
|
|
4120
4159
|
try {
|
|
4121
|
-
const token = execFileSync("gh", ["auth", "token"], {
|
|
4160
|
+
const token = execFileSync("gh", ["auth", "token"], {
|
|
4161
|
+
encoding: "utf-8"
|
|
4162
|
+
}).trim();
|
|
4122
4163
|
return token.length > 0;
|
|
4123
4164
|
} catch {
|
|
4124
4165
|
return false;
|
|
@@ -4134,9 +4175,13 @@ function hasSandboxCredentials(agent) {
|
|
|
4134
4175
|
}
|
|
4135
4176
|
function getAvailableAgentCommands(selectedAgent, sandbox) {
|
|
4136
4177
|
const isAvailable = (command) => commandExists(command) && (!sandbox || hasSandboxCredentials(command));
|
|
4137
|
-
const knownAvailable = KNOWN_RUNNER_AGENTS.filter(
|
|
4178
|
+
const knownAvailable = KNOWN_RUNNER_AGENTS.filter(
|
|
4179
|
+
(command) => isAvailable(command)
|
|
4180
|
+
);
|
|
4138
4181
|
const selectedAvailable = isAvailable(selectedAgent);
|
|
4139
|
-
if (selectedAvailable && !knownAvailable.includes(
|
|
4182
|
+
if (selectedAvailable && !knownAvailable.includes(
|
|
4183
|
+
selectedAgent
|
|
4184
|
+
)) {
|
|
4140
4185
|
return [selectedAgent, ...knownAvailable];
|
|
4141
4186
|
}
|
|
4142
4187
|
return knownAvailable;
|
|
@@ -4171,9 +4216,7 @@ function checkDockerAvailable() {
|
|
|
4171
4216
|
try {
|
|
4172
4217
|
execFileSync("docker", ["info"], { stdio: "ignore" });
|
|
4173
4218
|
} catch {
|
|
4174
|
-
console.error(
|
|
4175
|
-
chalk13.red("\u2717 Docker is not running or not installed.")
|
|
4176
|
-
);
|
|
4219
|
+
console.error(chalk13.red("\u2717 Docker is not running or not installed."));
|
|
4177
4220
|
console.error(
|
|
4178
4221
|
chalk13.yellow(
|
|
4179
4222
|
" The vem runner requires Docker to run agents in a secure sandbox."
|
|
@@ -4206,7 +4249,9 @@ function getSandboxImageDir() {
|
|
|
4206
4249
|
return dirname2(candidate);
|
|
4207
4250
|
}
|
|
4208
4251
|
}
|
|
4209
|
-
throw new Error(
|
|
4252
|
+
throw new Error(
|
|
4253
|
+
"Dockerfile.sandbox not found. Ensure the vem CLI is installed correctly."
|
|
4254
|
+
);
|
|
4210
4255
|
}
|
|
4211
4256
|
function buildSandboxImage() {
|
|
4212
4257
|
console.log(chalk13.cyan(" Building sandbox Docker image (first use)..."));
|
|
@@ -4220,7 +4265,9 @@ function buildSandboxImage() {
|
|
|
4220
4265
|
}
|
|
4221
4266
|
function ensureSandboxImage() {
|
|
4222
4267
|
try {
|
|
4223
|
-
execFileSync("docker", ["image", "inspect", SANDBOX_IMAGE_NAME], {
|
|
4268
|
+
execFileSync("docker", ["image", "inspect", SANDBOX_IMAGE_NAME], {
|
|
4269
|
+
stdio: "ignore"
|
|
4270
|
+
});
|
|
4224
4271
|
} catch {
|
|
4225
4272
|
buildSandboxImage();
|
|
4226
4273
|
}
|
|
@@ -4235,7 +4282,11 @@ function collectSandboxCredentials(agent) {
|
|
|
4235
4282
|
if (agent === "claude") {
|
|
4236
4283
|
addFromEnv("ANTHROPIC_API_KEY");
|
|
4237
4284
|
if (!creds.ANTHROPIC_API_KEY) {
|
|
4238
|
-
console.error(
|
|
4285
|
+
console.error(
|
|
4286
|
+
chalk13.red(
|
|
4287
|
+
`\u2717 ANTHROPIC_API_KEY is not set. Required for --agent claude.`
|
|
4288
|
+
)
|
|
4289
|
+
);
|
|
4239
4290
|
process.exit(1);
|
|
4240
4291
|
}
|
|
4241
4292
|
} else if (agent === "copilot" || agent === "gh") {
|
|
@@ -4244,31 +4295,43 @@ function collectSandboxCredentials(agent) {
|
|
|
4244
4295
|
creds.GITHUB_TOKEN = envToken;
|
|
4245
4296
|
} else {
|
|
4246
4297
|
try {
|
|
4247
|
-
const token = execFileSync("gh", ["auth", "token"], {
|
|
4298
|
+
const token = execFileSync("gh", ["auth", "token"], {
|
|
4299
|
+
encoding: "utf-8"
|
|
4300
|
+
}).trim();
|
|
4248
4301
|
if (token) creds.GITHUB_TOKEN = token;
|
|
4249
4302
|
} catch {
|
|
4250
4303
|
}
|
|
4251
4304
|
}
|
|
4252
4305
|
if (!creds.GITHUB_TOKEN) {
|
|
4253
|
-
console.error(
|
|
4254
|
-
|
|
4306
|
+
console.error(
|
|
4307
|
+
chalk13.red(`\u2717 GitHub token not found. Required for --agent copilot.`)
|
|
4308
|
+
);
|
|
4309
|
+
console.error(
|
|
4310
|
+
chalk13.gray(" Set GITHUB_TOKEN env var or run: gh auth login")
|
|
4311
|
+
);
|
|
4255
4312
|
process.exit(1);
|
|
4256
4313
|
}
|
|
4257
4314
|
} else if (agent === "gemini") {
|
|
4258
4315
|
addFromEnv("GEMINI_API_KEY");
|
|
4259
4316
|
if (!creds.GEMINI_API_KEY) {
|
|
4260
|
-
console.error(
|
|
4317
|
+
console.error(
|
|
4318
|
+
chalk13.red(`\u2717 GEMINI_API_KEY is not set. Required for --agent gemini.`)
|
|
4319
|
+
);
|
|
4261
4320
|
process.exit(1);
|
|
4262
4321
|
}
|
|
4263
4322
|
} else if (agent === "codex") {
|
|
4264
4323
|
addFromEnv("OPENAI_API_KEY");
|
|
4265
4324
|
if (!creds.OPENAI_API_KEY) {
|
|
4266
|
-
console.error(
|
|
4325
|
+
console.error(
|
|
4326
|
+
chalk13.red(`\u2717 OPENAI_API_KEY is not set. Required for --agent codex.`)
|
|
4327
|
+
);
|
|
4267
4328
|
process.exit(1);
|
|
4268
4329
|
}
|
|
4269
4330
|
}
|
|
4270
|
-
if (process.env.GIT_AUTHOR_NAME)
|
|
4271
|
-
|
|
4331
|
+
if (process.env.GIT_AUTHOR_NAME)
|
|
4332
|
+
creds.GIT_AUTHOR_NAME = process.env.GIT_AUTHOR_NAME;
|
|
4333
|
+
if (process.env.GIT_AUTHOR_EMAIL)
|
|
4334
|
+
creds.GIT_AUTHOR_EMAIL = process.env.GIT_AUTHOR_EMAIL;
|
|
4272
4335
|
return creds;
|
|
4273
4336
|
}
|
|
4274
4337
|
function sanitizeBranchSegment(value) {
|
|
@@ -4282,7 +4345,10 @@ async function resolveGitRemote(configService) {
|
|
|
4282
4345
|
const linkedRemote = (await configService.getLinkedRemoteName())?.trim();
|
|
4283
4346
|
const preferredRemote = linkedRemote || "origin";
|
|
4284
4347
|
try {
|
|
4285
|
-
return {
|
|
4348
|
+
return {
|
|
4349
|
+
name: preferredRemote,
|
|
4350
|
+
url: runGit(["remote", "get-url", preferredRemote])
|
|
4351
|
+
};
|
|
4286
4352
|
} catch {
|
|
4287
4353
|
if (preferredRemote !== "origin") {
|
|
4288
4354
|
try {
|
|
@@ -4333,23 +4399,33 @@ async function appendRunLogs(configService, apiKey, runId, entries) {
|
|
|
4333
4399
|
});
|
|
4334
4400
|
}
|
|
4335
4401
|
async function sendRunnerHeartbeat(configService, apiKey, projectId, status, currentTaskRunId, capabilities) {
|
|
4336
|
-
await apiRequest(
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4402
|
+
await apiRequest(
|
|
4403
|
+
configService,
|
|
4404
|
+
apiKey,
|
|
4405
|
+
`/projects/${projectId}/runners/heartbeat`,
|
|
4406
|
+
{
|
|
4407
|
+
method: "POST",
|
|
4408
|
+
body: JSON.stringify({
|
|
4409
|
+
status,
|
|
4410
|
+
current_task_run_id: currentTaskRunId,
|
|
4411
|
+
capabilities
|
|
4412
|
+
})
|
|
4413
|
+
}
|
|
4414
|
+
);
|
|
4344
4415
|
}
|
|
4345
4416
|
async function completeTaskRunWithRetry(configService, apiKey, runId, payload, attempts = 5) {
|
|
4346
4417
|
let lastError = "unknown error";
|
|
4347
4418
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
4348
4419
|
try {
|
|
4349
|
-
const response = await apiRequest(
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4420
|
+
const response = await apiRequest(
|
|
4421
|
+
configService,
|
|
4422
|
+
apiKey,
|
|
4423
|
+
`/task-runs/${runId}/complete`,
|
|
4424
|
+
{
|
|
4425
|
+
method: "POST",
|
|
4426
|
+
body: JSON.stringify(payload)
|
|
4427
|
+
}
|
|
4428
|
+
);
|
|
4353
4429
|
if (response.ok) return;
|
|
4354
4430
|
const bodyText = await response.text().catch(() => "");
|
|
4355
4431
|
lastError = `HTTP ${response.status}${bodyText ? `: ${bodyText}` : ""}`;
|
|
@@ -4363,7 +4439,15 @@ async function completeTaskRunWithRetry(configService, apiKey, runId, payload, a
|
|
|
4363
4439
|
throw new Error(`Failed to complete run ${runId}: ${lastError}`);
|
|
4364
4440
|
}
|
|
4365
4441
|
async function executeClaimedRun(input) {
|
|
4366
|
-
const {
|
|
4442
|
+
const {
|
|
4443
|
+
configService,
|
|
4444
|
+
apiKey,
|
|
4445
|
+
projectId,
|
|
4446
|
+
agent,
|
|
4447
|
+
useSandbox,
|
|
4448
|
+
agentPinned,
|
|
4449
|
+
run
|
|
4450
|
+
} = input;
|
|
4367
4451
|
const repoRoot = getRepoRoot3();
|
|
4368
4452
|
let sequence = 1;
|
|
4369
4453
|
let heartbeatTimer = null;
|
|
@@ -4390,7 +4474,11 @@ async function executeClaimedRun(input) {
|
|
|
4390
4474
|
} catch {
|
|
4391
4475
|
originalBranch = null;
|
|
4392
4476
|
}
|
|
4393
|
-
const preparedBranch = prepareTaskBranch(
|
|
4477
|
+
const preparedBranch = prepareTaskBranch(
|
|
4478
|
+
run.task_external_id,
|
|
4479
|
+
baseBranch,
|
|
4480
|
+
remote.name
|
|
4481
|
+
);
|
|
4394
4482
|
baseHash = preparedBranch.baseHash;
|
|
4395
4483
|
branchName = preparedBranch.branchName;
|
|
4396
4484
|
await appendRunLogs(configService, apiKey, run.id, [
|
|
@@ -4403,7 +4491,14 @@ async function executeClaimedRun(input) {
|
|
|
4403
4491
|
]);
|
|
4404
4492
|
const child = spawn3(
|
|
4405
4493
|
process.execPath,
|
|
4406
|
-
[
|
|
4494
|
+
[
|
|
4495
|
+
getCliEntrypoint(),
|
|
4496
|
+
"agent",
|
|
4497
|
+
agent,
|
|
4498
|
+
"--task",
|
|
4499
|
+
run.task_external_id,
|
|
4500
|
+
"--auto-exit"
|
|
4501
|
+
],
|
|
4407
4502
|
{
|
|
4408
4503
|
env: {
|
|
4409
4504
|
...process.env,
|
|
@@ -4473,15 +4568,13 @@ async function executeClaimedRun(input) {
|
|
|
4473
4568
|
{ sequence: sequence++, stream: "stderr", chunk: text }
|
|
4474
4569
|
]);
|
|
4475
4570
|
});
|
|
4476
|
-
const result = await new Promise(
|
|
4477
|
-
(
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
}
|
|
4484
|
-
);
|
|
4571
|
+
const result = await new Promise((resolve3) => {
|
|
4572
|
+
child.on("exit", (code, signal) => resolve3({ code, signal }));
|
|
4573
|
+
child.on("error", (error) => {
|
|
4574
|
+
completionError = error.message;
|
|
4575
|
+
resolve3({ code: null, signal: null });
|
|
4576
|
+
});
|
|
4577
|
+
});
|
|
4485
4578
|
exitCode = result.code;
|
|
4486
4579
|
if (completionError) {
|
|
4487
4580
|
completionStatus = cancellationRequested ? "cancelled" : "failed";
|
|
@@ -4503,7 +4596,11 @@ async function executeClaimedRun(input) {
|
|
|
4503
4596
|
if (completionStatus === "completed" && hasDirtyWorktree()) {
|
|
4504
4597
|
runGit(["add", "-A"], { stdio: "inherit" });
|
|
4505
4598
|
runGit(
|
|
4506
|
-
[
|
|
4599
|
+
[
|
|
4600
|
+
"commit",
|
|
4601
|
+
"-m",
|
|
4602
|
+
`chore(${run.task_external_id}): apply agent changes`
|
|
4603
|
+
],
|
|
4507
4604
|
{ stdio: "inherit" }
|
|
4508
4605
|
);
|
|
4509
4606
|
}
|
|
@@ -4599,15 +4696,19 @@ async function executeClaimedRunInSandbox(input) {
|
|
|
4599
4696
|
execFileSync("rm", ["-rf", worktreePath], { stdio: "ignore" });
|
|
4600
4697
|
}
|
|
4601
4698
|
console.log(chalk13.gray(` Cloning ${baseBranch} \u2192 ${worktreePath}`));
|
|
4602
|
-
execFileSync(
|
|
4603
|
-
"
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4699
|
+
execFileSync(
|
|
4700
|
+
"git",
|
|
4701
|
+
[
|
|
4702
|
+
"clone",
|
|
4703
|
+
"--quiet",
|
|
4704
|
+
`file://${repoRoot}`,
|
|
4705
|
+
"--branch",
|
|
4706
|
+
baseBranch,
|
|
4707
|
+
"--single-branch",
|
|
4708
|
+
worktreePath
|
|
4709
|
+
],
|
|
4710
|
+
{ stdio: "pipe" }
|
|
4711
|
+
);
|
|
4611
4712
|
runGitIn(worktreePath, ["checkout", "-b", branchName]);
|
|
4612
4713
|
if (remoteUrl) {
|
|
4613
4714
|
runGitIn(worktreePath, ["remote", "set-url", "origin", remoteUrl]);
|
|
@@ -4674,7 +4775,9 @@ async function executeClaimedRunInSandbox(input) {
|
|
|
4674
4775
|
if (dockerProcess?.pid) {
|
|
4675
4776
|
try {
|
|
4676
4777
|
if (containerName) {
|
|
4677
|
-
execFileSync("docker", ["stop", containerName], {
|
|
4778
|
+
execFileSync("docker", ["stop", containerName], {
|
|
4779
|
+
stdio: "ignore"
|
|
4780
|
+
});
|
|
4678
4781
|
} else {
|
|
4679
4782
|
dockerProcess.kill("SIGTERM");
|
|
4680
4783
|
}
|
|
@@ -4682,7 +4785,11 @@ async function executeClaimedRunInSandbox(input) {
|
|
|
4682
4785
|
}
|
|
4683
4786
|
}
|
|
4684
4787
|
await appendRunLogs(configService, apiKey, run.id, [
|
|
4685
|
-
{
|
|
4788
|
+
{
|
|
4789
|
+
sequence: sequence++,
|
|
4790
|
+
stream: "system",
|
|
4791
|
+
chunk: "Cancellation requested from web UI. Stopping sandbox container.\n"
|
|
4792
|
+
}
|
|
4686
4793
|
]);
|
|
4687
4794
|
}
|
|
4688
4795
|
if (maxRuntimeAt && !timedOut) {
|
|
@@ -4694,13 +4801,19 @@ async function executeClaimedRunInSandbox(input) {
|
|
|
4694
4801
|
if (dockerProcess?.pid) {
|
|
4695
4802
|
try {
|
|
4696
4803
|
if (containerName) {
|
|
4697
|
-
execFileSync("docker", ["stop", containerName], {
|
|
4804
|
+
execFileSync("docker", ["stop", containerName], {
|
|
4805
|
+
stdio: "ignore"
|
|
4806
|
+
});
|
|
4698
4807
|
}
|
|
4699
4808
|
} catch {
|
|
4700
4809
|
}
|
|
4701
4810
|
}
|
|
4702
4811
|
await appendRunLogs(configService, apiKey, run.id, [
|
|
4703
|
-
{
|
|
4812
|
+
{
|
|
4813
|
+
sequence: sequence++,
|
|
4814
|
+
stream: "system",
|
|
4815
|
+
chunk: "Run exceeded the maximum runtime. Stopping sandbox container.\n"
|
|
4816
|
+
}
|
|
4704
4817
|
]);
|
|
4705
4818
|
}
|
|
4706
4819
|
}
|
|
@@ -4728,7 +4841,10 @@ async function executeClaimedRunInSandbox(input) {
|
|
|
4728
4841
|
if (exitCode === 0 && !cancellationRequested && !timedOut) {
|
|
4729
4842
|
completionStatus = "completed";
|
|
4730
4843
|
try {
|
|
4731
|
-
const output = runGitIn(worktreePath, [
|
|
4844
|
+
const output = runGitIn(worktreePath, [
|
|
4845
|
+
"rev-list",
|
|
4846
|
+
`${baseHash}..HEAD`
|
|
4847
|
+
]);
|
|
4732
4848
|
commitHashes = output.split("\n").map((h) => h.trim()).filter(Boolean);
|
|
4733
4849
|
} catch {
|
|
4734
4850
|
}
|
|
@@ -4738,16 +4854,26 @@ async function executeClaimedRunInSandbox(input) {
|
|
|
4738
4854
|
}
|
|
4739
4855
|
if (completionStatus === "completed" && commitHashes.length > 0) {
|
|
4740
4856
|
try {
|
|
4741
|
-
runGitIn(worktreePath, ["push", "-u", "origin", branchName], {
|
|
4857
|
+
runGitIn(worktreePath, ["push", "-u", "origin", branchName], {
|
|
4858
|
+
stdio: "inherit"
|
|
4859
|
+
});
|
|
4742
4860
|
await appendRunLogs(configService, apiKey, run.id, [
|
|
4743
|
-
{
|
|
4744
|
-
|
|
4861
|
+
{
|
|
4862
|
+
sequence: sequence++,
|
|
4863
|
+
stream: "system",
|
|
4864
|
+
chunk: `Pushed branch ${branchName} to ${remote.name}.
|
|
4865
|
+
`
|
|
4866
|
+
}
|
|
4745
4867
|
]);
|
|
4746
4868
|
} catch (pushErr) {
|
|
4747
4869
|
const msg = pushErr instanceof Error ? pushErr.message : String(pushErr);
|
|
4748
4870
|
await appendRunLogs(configService, apiKey, run.id, [
|
|
4749
|
-
{
|
|
4750
|
-
|
|
4871
|
+
{
|
|
4872
|
+
sequence: sequence++,
|
|
4873
|
+
stream: "system",
|
|
4874
|
+
chunk: `Warning: failed to push branch: ${msg}
|
|
4875
|
+
`
|
|
4876
|
+
}
|
|
4751
4877
|
]);
|
|
4752
4878
|
createPr = false;
|
|
4753
4879
|
}
|
|
@@ -4761,8 +4887,12 @@ async function executeClaimedRunInSandbox(input) {
|
|
|
4761
4887
|
heartbeatTimer = null;
|
|
4762
4888
|
}
|
|
4763
4889
|
await appendRunLogs(configService, apiKey, run.id, [
|
|
4764
|
-
{
|
|
4765
|
-
|
|
4890
|
+
{
|
|
4891
|
+
sequence: sequence++,
|
|
4892
|
+
stream: "system",
|
|
4893
|
+
chunk: `Sandbox run error: ${msg}
|
|
4894
|
+
`
|
|
4895
|
+
}
|
|
4766
4896
|
]).catch(() => {
|
|
4767
4897
|
});
|
|
4768
4898
|
} finally {
|
|
@@ -4803,13 +4933,26 @@ ${run.user_prompt.trim()}` : "Triggered from VEM web."
|
|
|
4803
4933
|
}
|
|
4804
4934
|
async function appendTerminalLogs(configService, apiKey, sessionId, entries) {
|
|
4805
4935
|
if (entries.length === 0) return;
|
|
4806
|
-
await apiRequest(
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4936
|
+
await apiRequest(
|
|
4937
|
+
configService,
|
|
4938
|
+
apiKey,
|
|
4939
|
+
`/terminal-sessions/${sessionId}/logs`,
|
|
4940
|
+
{
|
|
4941
|
+
method: "POST",
|
|
4942
|
+
body: JSON.stringify({ entries })
|
|
4943
|
+
}
|
|
4944
|
+
);
|
|
4810
4945
|
}
|
|
4811
4946
|
async function executeClaimedTerminalSession(input) {
|
|
4812
|
-
const {
|
|
4947
|
+
const {
|
|
4948
|
+
configService,
|
|
4949
|
+
apiKey,
|
|
4950
|
+
projectId,
|
|
4951
|
+
agent,
|
|
4952
|
+
useSandbox,
|
|
4953
|
+
agentPinned,
|
|
4954
|
+
session
|
|
4955
|
+
} = input;
|
|
4813
4956
|
const repoRoot = getRepoRoot3();
|
|
4814
4957
|
let sequence = 2;
|
|
4815
4958
|
let heartbeatTimer = null;
|
|
@@ -4869,15 +5012,13 @@ $ ${session.command}
|
|
|
4869
5012
|
{ sequence: sequence++, stream: "stderr", chunk: text }
|
|
4870
5013
|
]);
|
|
4871
5014
|
});
|
|
4872
|
-
const result = await new Promise(
|
|
4873
|
-
(
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
}
|
|
4880
|
-
);
|
|
5015
|
+
const result = await new Promise((resolve3) => {
|
|
5016
|
+
child.on("exit", (code, signal) => resolve3({ code, signal }));
|
|
5017
|
+
child.on("error", (error) => {
|
|
5018
|
+
completionError = error.message;
|
|
5019
|
+
resolve3({ code: null, signal: null });
|
|
5020
|
+
});
|
|
5021
|
+
});
|
|
4881
5022
|
exitCode = result.code;
|
|
4882
5023
|
if (completionError) {
|
|
4883
5024
|
completionStatus = cancellationRequested ? "cancelled" : "failed";
|
|
@@ -4907,15 +5048,20 @@ $ ${session.command}
|
|
|
4907
5048
|
if (heartbeatTimer) {
|
|
4908
5049
|
clearInterval(heartbeatTimer);
|
|
4909
5050
|
}
|
|
4910
|
-
await apiRequest(
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
5051
|
+
await apiRequest(
|
|
5052
|
+
configService,
|
|
5053
|
+
apiKey,
|
|
5054
|
+
`/terminal-sessions/${session.id}/complete`,
|
|
5055
|
+
{
|
|
5056
|
+
method: "POST",
|
|
5057
|
+
body: JSON.stringify({
|
|
5058
|
+
status: completionStatus,
|
|
5059
|
+
exit_code: exitCode,
|
|
5060
|
+
error_message: completionError,
|
|
5061
|
+
terminal_reason: completionStatus === "cancelled" ? "Command cancelled from workspace UI." : null
|
|
5062
|
+
})
|
|
5063
|
+
}
|
|
5064
|
+
);
|
|
4919
5065
|
await sendRunnerHeartbeat(
|
|
4920
5066
|
configService,
|
|
4921
5067
|
apiKey,
|
|
@@ -4927,7 +5073,14 @@ $ ${session.command}
|
|
|
4927
5073
|
}
|
|
4928
5074
|
}
|
|
4929
5075
|
function registerRunnerCommands(program2) {
|
|
4930
|
-
program2.command("runner").description("Run a paired worker that executes queued web task runs").option(
|
|
5076
|
+
program2.command("runner").description("Run a paired worker that executes queued web task runs").option(
|
|
5077
|
+
"--agent <command>",
|
|
5078
|
+
"Agent command to launch for claimed tasks",
|
|
5079
|
+
"copilot"
|
|
5080
|
+
).option("--poll-interval <seconds>", "Polling interval in seconds", "10").option("--once", "Claim at most one run and then exit").option(
|
|
5081
|
+
"--unsafe",
|
|
5082
|
+
"Disable Docker sandbox (run agent directly on host \u2014 no isolation)"
|
|
5083
|
+
).action(async (options, command) => {
|
|
4931
5084
|
const configService = new ConfigService();
|
|
4932
5085
|
const apiKey = await ensureAuthenticated(configService);
|
|
4933
5086
|
const projectId = await configService.getProjectId();
|
|
@@ -4953,7 +5106,11 @@ function registerRunnerCommands(program2) {
|
|
|
4953
5106
|
)
|
|
4954
5107
|
);
|
|
4955
5108
|
if (!useSandbox) {
|
|
4956
|
-
console.log(
|
|
5109
|
+
console.log(
|
|
5110
|
+
chalk13.yellow(
|
|
5111
|
+
" \u26A0 Running in unsafe mode \u2014 agent has full host access."
|
|
5112
|
+
)
|
|
5113
|
+
);
|
|
4957
5114
|
}
|
|
4958
5115
|
let shouldStop = false;
|
|
4959
5116
|
let consecutiveErrors = 0;
|
|
@@ -4966,7 +5123,11 @@ function registerRunnerCommands(program2) {
|
|
|
4966
5123
|
const claimBackend = useSandbox ? "local_sandbox" : "local_runner";
|
|
4967
5124
|
while (!shouldStop) {
|
|
4968
5125
|
try {
|
|
4969
|
-
const capabilities = getRunnerCapabilities(
|
|
5126
|
+
const capabilities = getRunnerCapabilities(
|
|
5127
|
+
agent,
|
|
5128
|
+
useSandbox,
|
|
5129
|
+
agentPinned
|
|
5130
|
+
);
|
|
4970
5131
|
await sendRunnerHeartbeat(
|
|
4971
5132
|
configService,
|
|
4972
5133
|
apiKey,
|
|
@@ -4990,7 +5151,9 @@ function registerRunnerCommands(program2) {
|
|
|
4990
5151
|
);
|
|
4991
5152
|
if (!claimResponse.ok) {
|
|
4992
5153
|
const data = await claimResponse.json().catch(() => ({}));
|
|
4993
|
-
throw new Error(
|
|
5154
|
+
throw new Error(
|
|
5155
|
+
data.error || "Failed to claim task run"
|
|
5156
|
+
);
|
|
4994
5157
|
}
|
|
4995
5158
|
const payload = await claimResponse.json();
|
|
4996
5159
|
if (payload.run) {
|
|
@@ -5028,7 +5191,9 @@ function registerRunnerCommands(program2) {
|
|
|
5028
5191
|
);
|
|
5029
5192
|
if (!terminalClaimResponse.ok) {
|
|
5030
5193
|
const data = await terminalClaimResponse.json().catch(() => ({}));
|
|
5031
|
-
throw new Error(
|
|
5194
|
+
throw new Error(
|
|
5195
|
+
data.error || "Failed to claim terminal session"
|
|
5196
|
+
);
|
|
5032
5197
|
}
|
|
5033
5198
|
const terminalPayload = await terminalClaimResponse.json();
|
|
5034
5199
|
if (terminalPayload.session) {
|
|
@@ -6467,14 +6632,16 @@ Snapshot Contents:`));
|
|
|
6467
6632
|
console.log(chalk17.gray("Context updated."));
|
|
6468
6633
|
}
|
|
6469
6634
|
const configService = new ConfigService();
|
|
6470
|
-
await syncParsedTaskUpdatesToRemote(
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6635
|
+
await syncParsedTaskUpdatesToRemote(
|
|
6636
|
+
configService,
|
|
6637
|
+
update,
|
|
6638
|
+
result
|
|
6639
|
+
).catch((err) => {
|
|
6640
|
+
console.error(
|
|
6641
|
+
chalk17.yellow("[vem finalize] syncParsed failed:"),
|
|
6642
|
+
err instanceof Error ? err.message : String(err)
|
|
6643
|
+
);
|
|
6644
|
+
});
|
|
6478
6645
|
const synced = await syncProjectMemoryToRemote().catch(() => false);
|
|
6479
6646
|
if (synced) {
|
|
6480
6647
|
console.log(chalk17.gray("\u2714 Synced to cloud."));
|
|
@@ -7084,7 +7251,14 @@ function registerTaskCommands(program2) {
|
|
|
7084
7251
|
const tasks = await getDisplayTasks({ includeDeleted: true });
|
|
7085
7252
|
const status = typeof options.status === "string" ? options.status : void 0;
|
|
7086
7253
|
const cycleFilter = typeof options.cycle === "string" ? options.cycle.trim() : void 0;
|
|
7087
|
-
const validStatuses = /* @__PURE__ */ new Set([
|
|
7254
|
+
const validStatuses = /* @__PURE__ */ new Set([
|
|
7255
|
+
"todo",
|
|
7256
|
+
"ready",
|
|
7257
|
+
"in-review",
|
|
7258
|
+
"in-progress",
|
|
7259
|
+
"blocked",
|
|
7260
|
+
"done"
|
|
7261
|
+
]);
|
|
7088
7262
|
if (status && !validStatuses.has(status)) {
|
|
7089
7263
|
console.error(
|
|
7090
7264
|
chalk18.red(
|
|
@@ -7450,7 +7624,10 @@ ${currentSummary}
|
|
|
7450
7624
|
).option("-d, --description <description>", "Task description").option("--tags <tags>", "Comma-separated tags").option("--type <type>", "Task type (feature, bug, chore, spike, enabler)").option("--estimate-hours <hours>", "Estimated hours (e.g. 2.5)").option("--depends-on <ids>", "Comma-separated task IDs").option("--blocked-by <ids>", "Comma-separated task IDs").option("--recurrence <rule>", "Recurrence rule (weekly, monthly, cron)").option("--owner <id>", "Owner ID").option("--reviewer <id>", "Reviewer ID").option("--parent <id>", "Parent task ID").option("--order <number>", "Subtask order").option("--due-at <iso>", "Due date ISO string (YYYY-MM-DD)").option(
|
|
7451
7625
|
"--validation <steps>",
|
|
7452
7626
|
'Comma-separated validation steps (e.g. "pnpm build, pnpm test")'
|
|
7453
|
-
).option("--cycle <id>", "Assign to a cycle (e.g. CYCLE-001)").option(
|
|
7627
|
+
).option("--cycle <id>", "Assign to a cycle (e.g. CYCLE-001)").option(
|
|
7628
|
+
"--impact-score <score>",
|
|
7629
|
+
"Impact score 0-100 (RICE-based priority)"
|
|
7630
|
+
).option("--actor <name>", "Actor name for task creation").option("-r, --reasoning <reasoning>", "Reasoning for creation").action(async (title, options) => {
|
|
7454
7631
|
await trackCommandUsage("task add");
|
|
7455
7632
|
try {
|
|
7456
7633
|
let taskTitle = typeof title === "string" && title.trim().length > 0 ? title.trim() : void 0;
|
|
@@ -7797,7 +7974,9 @@ ${currentSummary}
|
|
|
7797
7974
|
const dueAt = parseDueAtIso(dueAtInput);
|
|
7798
7975
|
const normalizedType = typeInput?.trim().toLowerCase();
|
|
7799
7976
|
if (normalizedType && normalizedType !== "feature" && normalizedType !== "bug" && normalizedType !== "chore" && normalizedType !== "spike" && normalizedType !== "enabler") {
|
|
7800
|
-
throw new Error(
|
|
7977
|
+
throw new Error(
|
|
7978
|
+
"type must be feature, bug, chore, spike, or enabler."
|
|
7979
|
+
);
|
|
7801
7980
|
}
|
|
7802
7981
|
const taskType = normalizedType === "feature" || normalizedType === "bug" || normalizedType === "chore" || normalizedType === "spike" || normalizedType === "enabler" ? normalizedType : void 0;
|
|
7803
7982
|
let validationSteps = parseCommaList(validationInput);
|
|
@@ -7924,7 +8103,10 @@ ${currentSummary}
|
|
|
7924
8103
|
taskCmd.command("update <id>").description("Update task metadata").option("--tags <tags>", "Comma-separated tags").option("--type <type>", "Task type (feature, bug, chore, spike, enabler)").option("--estimate-hours <hours>", "Estimated hours (e.g. 2.5)").option("--depends-on <ids>", "Comma-separated task IDs").option("--blocked-by <ids>", "Comma-separated task IDs").option("--recurrence <rule>", "Recurrence rule (weekly, monthly, cron)").option("--owner <id>", "Owner ID").option("--reviewer <id>", "Reviewer ID").option("--parent <id>", "Parent task ID").option("--order <number>", "Subtask order").option("--due-at <iso>", "Due date ISO string (YYYY-MM-DD)").option(
|
|
7925
8104
|
"--validation <steps>",
|
|
7926
8105
|
"Set validation steps (comma-separated). Use empty string to clear."
|
|
7927
|
-
).option("--cycle <id>", "Assign to a cycle (e.g. CYCLE-001)").option(
|
|
8106
|
+
).option("--cycle <id>", "Assign to a cycle (e.g. CYCLE-001)").option(
|
|
8107
|
+
"--impact-score <score>",
|
|
8108
|
+
"Impact score 0-100 (RICE-based priority)"
|
|
8109
|
+
).option("--actor <name>", "Actor name for task update").option("-r, --reasoning <reasoning>", "Reasoning for update").action(async (id, options) => {
|
|
7928
8110
|
try {
|
|
7929
8111
|
const estimate = options.estimateHours !== void 0 ? Number.parseFloat(options.estimateHours) : void 0;
|
|
7930
8112
|
if (estimate !== void 0 && Number.isNaN(estimate)) {
|
|
@@ -8565,8 +8747,12 @@ No agent sessions attached to ${id} yet.`)
|
|
|
8565
8747
|
console.log(chalk18.bold(`
|
|
8566
8748
|
\u23F1 Flow Metrics: ${id} \u2014 ${task.title}
|
|
8567
8749
|
`));
|
|
8568
|
-
console.log(
|
|
8569
|
-
|
|
8750
|
+
console.log(
|
|
8751
|
+
` ${chalk18.gray("Lead time (created \u2192 done):")} ${fmtMs(metrics.lead_time_ms)}`
|
|
8752
|
+
);
|
|
8753
|
+
console.log(
|
|
8754
|
+
` ${chalk18.gray("Cycle time (started \u2192 done):")} ${fmtMs(metrics.cycle_time_ms)}`
|
|
8755
|
+
);
|
|
8570
8756
|
if (Object.keys(metrics.time_in_status).length > 0) {
|
|
8571
8757
|
console.log(chalk18.gray("\n Time in each status:"));
|
|
8572
8758
|
for (const [status, ms] of Object.entries(metrics.time_in_status)) {
|
|
@@ -8584,15 +8770,27 @@ No agent sessions attached to ${id} yet.`)
|
|
|
8584
8770
|
return chalk18.white(`${hrs}h`);
|
|
8585
8771
|
};
|
|
8586
8772
|
console.log(chalk18.bold("\n\u{1F4CA} Project Flow Summary\n"));
|
|
8587
|
-
console.log(
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
console.log(
|
|
8591
|
-
|
|
8773
|
+
console.log(
|
|
8774
|
+
` ${chalk18.gray("WIP (active tasks):")} ${chalk18.yellow(String(summary.wip_count))}`
|
|
8775
|
+
);
|
|
8776
|
+
console.log(
|
|
8777
|
+
` ${chalk18.gray("Throughput (last 7d):")} ${chalk18.white(String(summary.throughput_last_7d))} tasks`
|
|
8778
|
+
);
|
|
8779
|
+
console.log(
|
|
8780
|
+
` ${chalk18.gray("Throughput (last 30d):")} ${chalk18.white(String(summary.throughput_last_30d))} tasks`
|
|
8781
|
+
);
|
|
8782
|
+
console.log(
|
|
8783
|
+
` ${chalk18.gray("Avg cycle time:")} ${fmtMs(summary.avg_cycle_time_ms)}`
|
|
8784
|
+
);
|
|
8785
|
+
console.log(
|
|
8786
|
+
` ${chalk18.gray("Avg lead time:")} ${fmtMs(summary.avg_lead_time_ms)}`
|
|
8787
|
+
);
|
|
8592
8788
|
console.log();
|
|
8593
8789
|
}
|
|
8594
8790
|
} catch (error) {
|
|
8595
|
-
console.error(
|
|
8791
|
+
console.error(
|
|
8792
|
+
chalk18.red(`Failed to get flow metrics: ${error.message}`)
|
|
8793
|
+
);
|
|
8596
8794
|
}
|
|
8597
8795
|
});
|
|
8598
8796
|
taskCmd.command("score [id]").description("Show or set the impact score (0-100) for a task").option("--set <score>", "Set impact score manually (0-100)").option("-r, --reasoning <reasoning>", "Reasoning for score change").action(async (id, options) => {
|
|
@@ -8604,7 +8802,9 @@ No agent sessions attached to ${id} yet.`)
|
|
|
8604
8802
|
(t) => t.impact_score === void 0 && t.status !== "done" && !t.deleted_at
|
|
8605
8803
|
);
|
|
8606
8804
|
if (unscored.length === 0) {
|
|
8607
|
-
console.log(
|
|
8805
|
+
console.log(
|
|
8806
|
+
chalk18.green("\n\u2714 All active tasks have impact scores.\n")
|
|
8807
|
+
);
|
|
8608
8808
|
return;
|
|
8609
8809
|
}
|
|
8610
8810
|
const table = new Table4({
|
|
@@ -8622,9 +8822,13 @@ No agent sessions attached to ${id} yet.`)
|
|
|
8622
8822
|
}
|
|
8623
8823
|
console.log(chalk18.bold("\n\u{1F3AF} Impact Scores\n"));
|
|
8624
8824
|
console.log(table.toString());
|
|
8625
|
-
console.log(
|
|
8825
|
+
console.log(
|
|
8826
|
+
chalk18.gray(
|
|
8827
|
+
`
|
|
8626
8828
|
Unscored: ${unscored.length} task(s). Use: vem task score <id> --set <0-100>
|
|
8627
|
-
`
|
|
8829
|
+
`
|
|
8830
|
+
)
|
|
8831
|
+
);
|
|
8628
8832
|
return;
|
|
8629
8833
|
}
|
|
8630
8834
|
const task = await taskService.getTask(id);
|
|
@@ -8635,7 +8839,9 @@ No agent sessions attached to ${id} yet.`)
|
|
|
8635
8839
|
if (options.set !== void 0) {
|
|
8636
8840
|
const score = Number.parseFloat(options.set);
|
|
8637
8841
|
if (Number.isNaN(score) || score < 0 || score > 100) {
|
|
8638
|
-
console.error(
|
|
8842
|
+
console.error(
|
|
8843
|
+
chalk18.red("Score must be a number between 0 and 100.")
|
|
8844
|
+
);
|
|
8639
8845
|
process.exitCode = 1;
|
|
8640
8846
|
return;
|
|
8641
8847
|
}
|
|
@@ -8643,15 +8849,21 @@ No agent sessions attached to ${id} yet.`)
|
|
|
8643
8849
|
impact_score: score,
|
|
8644
8850
|
reasoning: options.reasoning
|
|
8645
8851
|
});
|
|
8646
|
-
console.log(
|
|
8852
|
+
console.log(
|
|
8853
|
+
chalk18.green(`
|
|
8647
8854
|
\u2714 Impact score for ${id} set to ${score}
|
|
8648
|
-
`)
|
|
8855
|
+
`)
|
|
8856
|
+
);
|
|
8649
8857
|
} else {
|
|
8650
8858
|
console.log(chalk18.bold(`
|
|
8651
8859
|
\u{1F3AF} ${id}: ${task.title}`));
|
|
8652
|
-
console.log(
|
|
8653
|
-
|
|
8654
|
-
|
|
8860
|
+
console.log(
|
|
8861
|
+
` Impact score: ${task.impact_score !== void 0 ? chalk18.yellow(String(Math.round(task.impact_score))) : chalk18.gray("not set")}`
|
|
8862
|
+
);
|
|
8863
|
+
console.log(
|
|
8864
|
+
chalk18.gray(` Set with: vem task score ${id} --set <0-100>
|
|
8865
|
+
`)
|
|
8866
|
+
);
|
|
8655
8867
|
}
|
|
8656
8868
|
} catch (error) {
|
|
8657
8869
|
console.error(chalk18.red(`Failed to manage score: ${error.message}`));
|
|
@@ -8744,11 +8956,11 @@ async function initServerMonitoring(config) {
|
|
|
8744
8956
|
await initServerMonitoring({
|
|
8745
8957
|
dsn: "https://ed007f2c213d0aa07c1be256ca51750c@o4510863861612544.ingest.de.sentry.io/4510863921774672",
|
|
8746
8958
|
environment: process.env.NODE_ENV || "production",
|
|
8747
|
-
release: "0.1.
|
|
8959
|
+
release: "0.1.54",
|
|
8748
8960
|
serviceName: "cli"
|
|
8749
8961
|
});
|
|
8750
8962
|
var program = new Command();
|
|
8751
|
-
program.name("vem").description("vem Project Memory CLI").version("0.1.
|
|
8963
|
+
program.name("vem").description("vem Project Memory CLI").version("0.1.54").addHelpText(
|
|
8752
8964
|
"after",
|
|
8753
8965
|
`
|
|
8754
8966
|
${chalk19.bold("\n\u26A1 Power Workflows:")}
|