@evo-dev/evodev 0.0.1-alpha.12 → 0.0.1-alpha.13

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 CHANGED
@@ -223,7 +223,7 @@ var require_lib = __commonJS((exports, module) => {
223
223
 
224
224
  // packages/cli/src/index.ts
225
225
  import { realpathSync } from "node:fs";
226
- import { resolve as resolve13 } from "node:path";
226
+ import { resolve as resolve14 } from "node:path";
227
227
  import { fileURLToPath as fileURLToPath5 } from "node:url";
228
228
 
229
229
  // packages/core/src/agents/index.ts
@@ -21252,7 +21252,9 @@ function resolveDefaultAssetsRootDir(importMetaUrl = import.meta.url) {
21252
21252
  }
21253
21253
 
21254
21254
  // packages/cli/src/init.ts
21255
- import { stat as stat16 } from "node:fs/promises";
21255
+ import { realpath as realpath2, stat as stat16 } from "node:fs/promises";
21256
+ import { tmpdir } from "node:os";
21257
+ import { isAbsolute as isAbsolute9, parse as parse2, relative as relative10, resolve as resolve9, sep as sep3 } from "node:path";
21256
21258
 
21257
21259
  // node_modules/.bun/@inquirer+core@11.2.1+6983e0b160ab4824/node_modules/@inquirer/core/dist/lib/key.js
21258
21260
  var keybindings = ["emacs", "vim"];
@@ -24847,9 +24849,12 @@ var DEFAULT_INIT_ANSWERS = {
24847
24849
  initializeConfig: true,
24848
24850
  registerSkills: true,
24849
24851
  registerAgents: true,
24852
+ registerRecommendedProjects: false,
24850
24853
  runDoctor: true
24851
24854
  };
24852
- var INIT_TOTAL_STEPS = 6;
24855
+ var INIT_TOTAL_STEPS = 7;
24856
+ var INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS = 90;
24857
+ var MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
24853
24858
  async function runInit(options = {}) {
24854
24859
  const homeDir = resolveHomeDir3(options.homeDir);
24855
24860
  const assetsRootDir = options.assetsRootDir ?? resolveDefaultAssetsRootDir();
@@ -24920,6 +24925,7 @@ async function runInit(options = {}) {
24920
24925
  pluginInstallResults: [],
24921
24926
  hookInstallResults: [],
24922
24927
  syncResult: null,
24928
+ projectRegistrationResults: [],
24923
24929
  doctorRan: true,
24924
24930
  warnings: warnings2
24925
24931
  };
@@ -24998,6 +25004,7 @@ async function runInit(options = {}) {
24998
25004
  pluginInstallResults,
24999
25005
  hookInstallResults: hookInstallResults2,
25000
25006
  syncResult: syncResult2,
25007
+ projectRegistrationResults: [],
25001
25008
  warnings: warnings2
25002
25009
  }));
25003
25010
  return {
@@ -25015,6 +25022,7 @@ async function runInit(options = {}) {
25015
25022
  pluginInstallResults,
25016
25023
  hookInstallResults: hookInstallResults2,
25017
25024
  syncResult: syncResult2,
25025
+ projectRegistrationResults: [],
25018
25026
  doctorRan: doctorRan2,
25019
25027
  warnings: warnings2
25020
25028
  };
@@ -25058,11 +25066,19 @@ async function runInit(options = {}) {
25058
25066
  detail: formatSyncProgressDetail(syncResult)
25059
25067
  });
25060
25068
  }
25069
+ const projectRegistrationResults = await registerInitProjects({
25070
+ homeDir,
25071
+ now: timestamp,
25072
+ autoRegisterRecommended: options.answers?.registerRecommendedProjects === true,
25073
+ skipProjectSelection: options.answers?.registerRecommendedProjects === false,
25074
+ prompter: options.prompter,
25075
+ progress: options.progress
25076
+ });
25061
25077
  const doctorRan = doctorResult !== null;
25062
- const warnings = collectWarnings(syncResult, hookInstallResults, answers.runDoctor, doctorRan);
25078
+ const warnings = collectWarnings(syncResult, hookInstallResults, projectRegistrationResults, answers.runDoctor, doctorRan);
25063
25079
  emitInitProgress(options.progress, {
25064
25080
  status: "done",
25065
- current: 6,
25081
+ current: 7,
25066
25082
  label: "Finish init",
25067
25083
  detail: warnings.length === 0 ? "ready" : `${warnings.length} warning(s)`
25068
25084
  });
@@ -25080,6 +25096,7 @@ async function runInit(options = {}) {
25080
25096
  pluginInstallResults,
25081
25097
  hookInstallResults,
25082
25098
  syncResult,
25099
+ projectRegistrationResults,
25083
25100
  warnings
25084
25101
  }));
25085
25102
  return {
@@ -25097,6 +25114,7 @@ async function runInit(options = {}) {
25097
25114
  pluginInstallResults,
25098
25115
  hookInstallResults,
25099
25116
  syncResult,
25117
+ projectRegistrationResults,
25100
25118
  doctorRan,
25101
25119
  warnings
25102
25120
  };
@@ -25139,6 +25157,7 @@ function formatInitSummary(input) {
25139
25157
  ...result.warnings.map((warning) => ` ⚠️ ${warning}`),
25140
25158
  ...result.errors.map((error) => ` ❌ ${error}`)
25141
25159
  ]) ?? [" - skipped"];
25160
+ const projectLines = formatProjectRegistrationLines(input.projectRegistrationResults);
25142
25161
  const warningLines = formatSummaryWarningLines(input.warnings, input.syncResult);
25143
25162
  return [
25144
25163
  "EvoDev init result",
@@ -25146,7 +25165,7 @@ function formatInitSummary(input) {
25146
25165
  ...EVOHUB_BANNER,
25147
25166
  "",
25148
25167
  failedPluginInstall ? "❌ Status: stopped" : "✅ Status: ready",
25149
- failedPluginInstall ? "Progress: stopped at Code Agent plugin install" : "Progress: 6/6 complete",
25168
+ failedPluginInstall ? "Progress: stopped at Code Agent plugin install" : "Progress: 7/7 complete",
25150
25169
  `Selected Code Agents: ${input.selectedPlugins.length > 0 ? input.selectedPlugins.join(", ") : "none"}`,
25151
25170
  `Settings: ${input.settingsPath}`,
25152
25171
  `Registry: ${input.registryPath}`,
@@ -25164,6 +25183,8 @@ function formatInitSummary(input) {
25164
25183
  ...hookInstallLines,
25165
25184
  "Sync:",
25166
25185
  ...syncLines,
25186
+ "Projects:",
25187
+ ...projectLines,
25167
25188
  "Warnings:",
25168
25189
  ...warningLines
25169
25190
  ].join(`
@@ -25191,6 +25212,8 @@ function formatInitStoppedSummary(input) {
25191
25212
  " - skipped",
25192
25213
  "Sync:",
25193
25214
  " - skipped",
25215
+ "Projects:",
25216
+ " - skipped",
25194
25217
  "Warnings:",
25195
25218
  ...formatSimpleWarningLines(input.warnings),
25196
25219
  ...doctorOutputLines,
@@ -25219,6 +25242,14 @@ function formatHookInstallLines(results) {
25219
25242
  ...result.warnings.map((warning) => ` ⚠️ ${warning}`)
25220
25243
  ]);
25221
25244
  }
25245
+ function formatProjectRegistrationLines(results) {
25246
+ if (results.length === 0)
25247
+ return [" - no new projects registered"];
25248
+ return results.map((result) => {
25249
+ const icon = result.status === "failed" ? "❌" : "✅";
25250
+ return ` ${icon} ${result.displayName} (${result.projectKey}): ${result.status}`;
25251
+ });
25252
+ }
25222
25253
  function formatPluginInstallIcon(status) {
25223
25254
  switch (status) {
25224
25255
  case "installed":
@@ -25305,6 +25336,17 @@ function createInteractivePrompter(promptApi = defaultPromptApi) {
25305
25336
  required: true,
25306
25337
  validate: (choices) => choices.length > 0 || "Select at least one Code Agent."
25307
25338
  }));
25339
+ },
25340
+ async selectProjects(candidates2) {
25341
+ const selected = await promptApi.checkbox({
25342
+ message: "Select projects to include in EvoDev progression",
25343
+ choices: createInitProjectChoices(candidates2),
25344
+ required: false,
25345
+ pageSize: 15,
25346
+ validate: () => true
25347
+ });
25348
+ const candidateKeys = new Set(candidates2.map((candidate) => candidate.projectKey));
25349
+ return unique2(selected.filter((projectKey) => candidateKeys.has(projectKey)));
25308
25350
  }
25309
25351
  };
25310
25352
  }
@@ -25327,6 +25369,160 @@ function createCodeAgentChoices(defaultValue) {
25327
25369
  }
25328
25370
  ];
25329
25371
  }
25372
+ function createInitProjectChoices(candidates2) {
25373
+ return candidates2.map((candidate) => ({
25374
+ value: candidate.projectKey,
25375
+ name: `${candidate.displayName} (${candidate.workspaceKind})`,
25376
+ description: `${candidate.workspaceRoot} · ${candidate.recommendationReason}`,
25377
+ checked: candidate.recommended
25378
+ }));
25379
+ }
25380
+ async function resolveInitProjectCandidates(input) {
25381
+ const now = input.now instanceof Date ? input.now.getTime() : Date.parse(input.now ?? "");
25382
+ const nowMs = Number.isFinite(now) ? now : Date.now();
25383
+ const cutoff = nowMs - INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS * MILLISECONDS_PER_DAY;
25384
+ const [discoveredProjects, registeredProjects, homeRoot, temporaryRoots] = await Promise.all([
25385
+ listDiscoveredProjects({ homeDir: input.homeDir }),
25386
+ listRegisteredProjects({ homeDir: input.homeDir }),
25387
+ canonicalExistingPath(input.homeDir),
25388
+ resolveTemporaryRoots()
25389
+ ]);
25390
+ const registeredKeys = new Set(registeredProjects.map((project) => project.projectKey));
25391
+ const candidates2 = [];
25392
+ for (const discovered of discoveredProjects) {
25393
+ if (registeredKeys.has(discovered.projectKey))
25394
+ continue;
25395
+ const candidate = await resolveInitProjectCandidate({
25396
+ homeDir: input.homeDir,
25397
+ homeRoot,
25398
+ temporaryRoots,
25399
+ cutoff,
25400
+ discovered
25401
+ });
25402
+ if (candidate !== null)
25403
+ candidates2.push(candidate);
25404
+ }
25405
+ return candidates2;
25406
+ }
25407
+ async function resolveInitProjectCandidate(input) {
25408
+ const workspace = await resolveProjectWorkspaceFromCwd({
25409
+ homeDir: input.homeDir,
25410
+ cwd: input.discovered.workspaceRoot
25411
+ }).catch(() => null);
25412
+ if (workspace === null || workspace.projectKey !== input.discovered.projectKey || workspace.workspaceRoot !== input.discovered.workspaceRoot || workspace.workspaceKind !== input.discovered.workspaceKind) {
25413
+ return null;
25414
+ }
25415
+ const lastSeenAt = Date.parse(input.discovered.lastSeenAt);
25416
+ const recentlySeen = Number.isFinite(lastSeenAt) && lastSeenAt >= input.cutoff;
25417
+ const insideHome = input.homeRoot !== null && isPathWithin(input.homeRoot, workspace.workspaceRoot);
25418
+ const excludedLocation = workspace.workspaceRoot === parse2(workspace.workspaceRoot).root || workspace.workspaceRoot === input.homeRoot || !insideHome && input.temporaryRoots.some((root) => isPathWithin(root, workspace.workspaceRoot));
25419
+ const recommended = workspace.workspaceKind === "git" && recentlySeen && !excludedLocation;
25420
+ const recommendationReason = recommended ? `recommended: Git workspace seen within ${INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS} days` : workspace.workspaceKind === "directory" ? "not preselected: directory workspace" : excludedLocation ? "not preselected: root, HOME, or temporary location" : `not preselected: not seen within ${INIT_RECOMMENDED_PROJECT_MAX_AGE_DAYS} days`;
25421
+ return {
25422
+ projectKey: input.discovered.projectKey,
25423
+ displayName: input.discovered.displayName,
25424
+ workspaceRoot: workspace.workspaceRoot,
25425
+ workspaceKind: workspace.workspaceKind,
25426
+ lastSeenAt: input.discovered.lastSeenAt,
25427
+ recommended,
25428
+ recommendationReason
25429
+ };
25430
+ }
25431
+ async function registerInitProjects(input) {
25432
+ const canPrompt = !input.skipProjectSelection && input.prompter?.selectProjects !== undefined;
25433
+ if (!input.autoRegisterRecommended && !canPrompt) {
25434
+ emitInitProgress(input.progress, {
25435
+ status: "skipped",
25436
+ current: 6,
25437
+ label: "Register projects",
25438
+ detail: "disabled"
25439
+ });
25440
+ return [];
25441
+ }
25442
+ const candidates2 = await resolveInitProjectCandidates({
25443
+ homeDir: input.homeDir,
25444
+ now: input.now
25445
+ });
25446
+ if (candidates2.length === 0) {
25447
+ emitInitProgress(input.progress, {
25448
+ status: "skipped",
25449
+ current: 6,
25450
+ label: "Register projects",
25451
+ detail: "no valid discovered workspaces"
25452
+ });
25453
+ return [];
25454
+ }
25455
+ let selectedProjectKeys = [];
25456
+ if (input.autoRegisterRecommended) {
25457
+ selectedProjectKeys = candidates2.filter((candidate) => candidate.recommended).map((candidate) => candidate.projectKey);
25458
+ } else if (input.prompter?.selectProjects !== undefined) {
25459
+ await input.progress?.clear?.();
25460
+ selectedProjectKeys = await input.prompter.selectProjects(candidates2);
25461
+ }
25462
+ const candidatesByKey = new Map(candidates2.map((candidate) => [candidate.projectKey, candidate]));
25463
+ const selectedCandidates = unique2(selectedProjectKeys).map((projectKey) => candidatesByKey.get(projectKey)).filter((candidate) => candidate !== undefined);
25464
+ if (selectedCandidates.length === 0) {
25465
+ emitInitProgress(input.progress, {
25466
+ status: "skipped",
25467
+ current: 6,
25468
+ label: "Register projects",
25469
+ detail: input.autoRegisterRecommended ? "no recommended Git workspaces" : "none selected"
25470
+ });
25471
+ return [];
25472
+ }
25473
+ emitInitProgress(input.progress, {
25474
+ status: "doing",
25475
+ current: 6,
25476
+ label: "Register projects",
25477
+ detail: `${selectedCandidates.length} selected`
25478
+ });
25479
+ const results = [];
25480
+ for (const candidate of selectedCandidates) {
25481
+ try {
25482
+ const registration = await registerDiscoveredProject({
25483
+ homeDir: input.homeDir,
25484
+ projectKey: candidate.projectKey,
25485
+ now: input.now
25486
+ });
25487
+ results.push({
25488
+ projectKey: candidate.projectKey,
25489
+ displayName: candidate.displayName,
25490
+ status: registration.changed ? "registered" : "already-registered",
25491
+ path: registration.path
25492
+ });
25493
+ } catch {
25494
+ results.push({
25495
+ projectKey: candidate.projectKey,
25496
+ displayName: candidate.displayName,
25497
+ status: "failed",
25498
+ path: null
25499
+ });
25500
+ }
25501
+ }
25502
+ const failed = results.filter((result) => result.status === "failed").length;
25503
+ emitInitProgress(input.progress, {
25504
+ status: failed === 0 ? "done" : "failed",
25505
+ current: 6,
25506
+ label: "Register projects",
25507
+ detail: failed === 0 ? `${results.length} registered` : `${results.length - failed} registered, ${failed} failed`
25508
+ });
25509
+ return results;
25510
+ }
25511
+ async function resolveTemporaryRoots() {
25512
+ const roots = await Promise.all([tmpdir(), "/tmp", "/private/tmp", "/var/tmp"].map(canonicalExistingPath));
25513
+ return unique2(roots.filter((root) => root !== null));
25514
+ }
25515
+ async function canonicalExistingPath(path2) {
25516
+ try {
25517
+ return await realpath2(resolve9(path2));
25518
+ } catch {
25519
+ return null;
25520
+ }
25521
+ }
25522
+ function isPathWithin(root, candidate) {
25523
+ const path2 = relative10(root, candidate);
25524
+ return path2 === "" || path2 !== ".." && !path2.startsWith(`..${sep3}`) && !isAbsolute9(path2);
25525
+ }
25330
25526
  function createInitSettingsInput(selectedPlugins, answers, existingSettings) {
25331
25527
  const plugins2 = {};
25332
25528
  if (existingSettings === null) {
@@ -25518,10 +25714,11 @@ async function maybeRunDoctor(input) {
25518
25714
  const result = await input.runDoctor(input.context);
25519
25715
  return result ?? { exitCode: 0 };
25520
25716
  }
25521
- function collectWarnings(syncResult, hookInstallResults, requestedDoctor, doctorRan) {
25717
+ function collectWarnings(syncResult, hookInstallResults, projectRegistrationResults, requestedDoctor, doctorRan) {
25522
25718
  const warnings = [
25523
25719
  ...syncResult?.results.flatMap((result) => result.warnings) ?? [],
25524
- ...hookInstallResults.flatMap((result) => result.warnings)
25720
+ ...hookInstallResults.flatMap((result) => result.warnings),
25721
+ ...projectRegistrationResults.filter((result) => result.status === "failed").map((result) => `Project ${result.projectKey} could not be registered.`)
25525
25722
  ];
25526
25723
  if (requestedDoctor && !doctorRan) {
25527
25724
  warnings.push("Doctor command was requested, but no doctor hook was provided; run `evodev doctor` manually after init.");
@@ -26159,15 +26356,15 @@ function throwReviewWriteError(error) {
26159
26356
  // packages/cli/src/session-knowledge.ts
26160
26357
  import { spawn as spawn3 } from "node:child_process";
26161
26358
  import { createHash as createHash5 } from "node:crypto";
26162
- import { mkdtemp, readFile as readFile28, realpath as realpath3, rm as rm8, stat as stat19, writeFile as writeFile18 } from "node:fs/promises";
26163
- import { tmpdir } from "node:os";
26164
- import { dirname as dirname23, join as join30, resolve as resolve9 } from "node:path";
26359
+ import { mkdtemp, readFile as readFile28, realpath as realpath4, rm as rm8, stat as stat19, writeFile as writeFile18 } from "node:fs/promises";
26360
+ import { tmpdir as tmpdir2 } from "node:os";
26361
+ import { dirname as dirname23, join as join30, resolve as resolve10 } from "node:path";
26165
26362
 
26166
26363
  // packages/cli/src/ui/session-evidence.ts
26167
26364
  import { createHash as createHash4 } from "node:crypto";
26168
26365
  import { createReadStream } from "node:fs";
26169
- import { readFile as readFile27, realpath as realpath2, stat as stat18 } from "node:fs/promises";
26170
- import { isAbsolute as isAbsolute9, relative as relative10 } from "node:path";
26366
+ import { readFile as readFile27, realpath as realpath3, stat as stat18 } from "node:fs/promises";
26367
+ import { isAbsolute as isAbsolute10, relative as relative11 } from "node:path";
26171
26368
  import { createInterface as createInterface3 } from "node:readline";
26172
26369
  var MAX_TRANSCRIPT_BYTES = 32 * 1024 * 1024;
26173
26370
  var MAX_MESSAGE_BYTES = 64 * 1024;
@@ -26400,19 +26597,19 @@ function findTranscriptPointer(events) {
26400
26597
  return null;
26401
26598
  }
26402
26599
  async function resolveTrustedTranscriptPath(input) {
26403
- if (!isAbsolute9(input.path) || !input.path.endsWith(".jsonl")) {
26600
+ if (!isAbsolute10(input.path) || !input.path.endsWith(".jsonl")) {
26404
26601
  throw new Error("Native transcript path is not an absolute JSONL file.");
26405
26602
  }
26406
26603
  const targets = input.target === "claude" ? [{ target: "claude", root: `${input.homeDir}/.claude/projects` }] : input.target === "codex" ? [{ target: "codex", root: `${input.homeDir}/.codex/sessions` }] : [
26407
26604
  { target: "claude", root: `${input.homeDir}/.claude/projects` },
26408
26605
  { target: "codex", root: `${input.homeDir}/.codex/sessions` }
26409
26606
  ];
26410
- const resolvedPath = await realpath2(input.path);
26607
+ const resolvedPath = await realpath3(input.path);
26411
26608
  for (const candidate of targets) {
26412
26609
  try {
26413
- const resolvedRoot = await realpath2(candidate.root);
26414
- const child = relative10(resolvedRoot, resolvedPath);
26415
- if (child !== "" && !child.startsWith("..") && !isAbsolute9(child)) {
26610
+ const resolvedRoot = await realpath3(candidate.root);
26611
+ const child = relative11(resolvedRoot, resolvedPath);
26612
+ if (child !== "" && !child.startsWith("..") && !isAbsolute10(child)) {
26416
26613
  return { path: resolvedPath, target: candidate.target };
26417
26614
  }
26418
26615
  } catch {}
@@ -26861,7 +27058,7 @@ function createSessionKnowledgeDistiller(input = {}) {
26861
27058
  }
26862
27059
  function createCodexSessionKnowledgeAnalyzer(input = {}) {
26863
27060
  return async (evidence) => {
26864
- const workDir = await mkdtemp(join30(tmpdir(), "evodev-session-knowledge-"));
27061
+ const workDir = await mkdtemp(join30(tmpdir2(), "evodev-session-knowledge-"));
26865
27062
  const schemaPath = join30(workDir, "output.schema.json");
26866
27063
  const outputPath = join30(workDir, "output.json");
26867
27064
  try {
@@ -27204,8 +27401,8 @@ async function resolveSegmentRepoRoot(events, homeDir) {
27204
27401
  for (const event of [...events].reverse()) {
27205
27402
  if (!isRecord17(event.payload) || typeof event.payload.cwd !== "string")
27206
27403
  continue;
27207
- const cwd = await realpath3(event.payload.cwd).catch(() => null);
27208
- if (cwd === null || cwd === resolve9(homeDir) || !await isDirectory3(cwd))
27404
+ const cwd = await realpath4(event.payload.cwd).catch(() => null);
27405
+ if (cwd === null || cwd === resolve10(homeDir) || !await isDirectory3(cwd))
27209
27406
  continue;
27210
27407
  return findRepositoryRoot(cwd, homeDir);
27211
27408
  }
@@ -27217,15 +27414,15 @@ async function resolveHistoricalProjectRepoRoot(projectKey, homeDir) {
27217
27414
  const workspaceRoot = registered?.workspaceRoot ?? discovered?.workspaceRoot;
27218
27415
  if (workspaceRoot === undefined)
27219
27416
  return null;
27220
- const trusted = await realpath3(workspaceRoot).catch(() => null);
27221
- if (trusted === null || trusted === resolve9(homeDir) || !await isDirectory3(trusted)) {
27417
+ const trusted = await realpath4(workspaceRoot).catch(() => null);
27418
+ if (trusted === null || trusted === resolve10(homeDir) || !await isDirectory3(trusted)) {
27222
27419
  return null;
27223
27420
  }
27224
27421
  return findRepositoryRoot(trusted, homeDir);
27225
27422
  }
27226
27423
  async function findRepositoryRoot(cwd, homeDir) {
27227
27424
  let current = cwd;
27228
- const stop = resolve9(homeDir);
27425
+ const stop = resolve10(homeDir);
27229
27426
  while (current !== dirname23(current) && current !== stop) {
27230
27427
  if (await pathExists9(join30(current, ".git")))
27231
27428
  return current;
@@ -27455,7 +27652,7 @@ var SEMANTIC_KNOWLEDGE_SCHEMA = {
27455
27652
  import { createHash as createHash7 } from "node:crypto";
27456
27653
  import { constants as constants2 } from "node:fs";
27457
27654
  import { lstat as lstat4, open as open4 } from "node:fs/promises";
27458
- import { basename as basename6, resolve as resolve10 } from "node:path";
27655
+ import { basename as basename6, resolve as resolve11 } from "node:path";
27459
27656
 
27460
27657
  // node_modules/.bun/@letta-ai+trajectory@0.2.0/node_modules/@letta-ai/trajectory/dist/types.js
27461
27658
  class NormalizationError extends Error {
@@ -29631,7 +29828,7 @@ var TRANSCRIPT_SOURCES = [
29631
29828
  ];
29632
29829
  async function runTrajectoryImportCommand(argv, options = {}) {
29633
29830
  const flags = parseTrajectoryImportFlags(argv);
29634
- const transcriptPath = resolve10(options.cwd ?? process.cwd(), flags.path);
29831
+ const transcriptPath = resolve11(options.cwd ?? process.cwd(), flags.path);
29635
29832
  const prepared = await prepareTrajectoryImport({
29636
29833
  source: flags.source,
29637
29834
  transcriptPath,
@@ -29668,7 +29865,7 @@ async function previewTrajectoryImport(input) {
29668
29865
  })).plan.preview;
29669
29866
  }
29670
29867
  async function prepareTrajectoryImport(input) {
29671
- const transcriptPath = resolve10(input.transcriptPath);
29868
+ const transcriptPath = resolve11(input.transcriptPath);
29672
29869
  const bytes = await readStableTrajectoryImportFile(transcriptPath);
29673
29870
  const transcript = decodeUtf8(bytes);
29674
29871
  const normalized = normalizeCanonicalSafely(input.source, transcript);
@@ -30526,17 +30723,17 @@ async function startEvolutionReviewServer(input) {
30526
30723
  response.end(error instanceof Error ? error.message : String(error));
30527
30724
  }
30528
30725
  });
30529
- await new Promise((resolve11) => {
30530
- server.listen(input.flags.port, input.flags.host, resolve11);
30726
+ await new Promise((resolve12) => {
30727
+ server.listen(input.flags.port, input.flags.host, resolve12);
30531
30728
  });
30532
30729
  const address = server.address();
30533
30730
  const port = typeof address === "object" && address !== null ? address.port : input.flags.port;
30534
30731
  input.write(`EvoDev evolution review server: http://${input.flags.host}:${port}/?token=${token}`);
30535
30732
  input.write("Mode: local-only read snapshot. Mutation actions are not implemented in this slice.");
30536
30733
  input.write("Press Ctrl+C to stop.");
30537
- return await new Promise((resolve11) => {
30734
+ return await new Promise((resolve12) => {
30538
30735
  const close = () => {
30539
- server.close(() => resolve11(0));
30736
+ server.close(() => resolve12(0));
30540
30737
  };
30541
30738
  process.once("SIGINT", close);
30542
30739
  process.once("SIGTERM", close);
@@ -31828,8 +32025,8 @@ import { dirname as dirname26, join as join34 } from "node:path";
31828
32025
 
31829
32026
  // packages/cli/src/improvement-eval.ts
31830
32027
  import { spawn as spawn4 } from "node:child_process";
31831
- import { mkdtemp as mkdtemp2, readFile as readFile31, realpath as realpath4, rm as rm9, stat as stat21, writeFile as writeFile20 } from "node:fs/promises";
31832
- import { tmpdir as tmpdir2 } from "node:os";
32028
+ import { mkdtemp as mkdtemp2, readFile as readFile31, realpath as realpath5, rm as rm9, stat as stat21, writeFile as writeFile20 } from "node:fs/promises";
32029
+ import { tmpdir as tmpdir3 } from "node:os";
31833
32030
  import { join as join32 } from "node:path";
31834
32031
  var DEFAULT_EVAL_LIMIT = 1;
31835
32032
  var MODEL_TIMEOUT_MS2 = 2 * 60 * 1000;
@@ -31838,7 +32035,7 @@ var MAX_CANDIDATE_CHARS = 20000;
31838
32035
  var MAX_PROCESS_OUTPUT_CHARS = 256000;
31839
32036
  async function runPendingImprovementEvaluations(options) {
31840
32037
  const snapshot = await readEvolutionReviewSnapshot({ homeDir: options.homeDir });
31841
- const pending = snapshot.repoProposals.filter((proposal) => proposal.reviewState === "accepted" && proposal.improvementEval?.requested === true && (proposal.improvementEval.status === "awaiting-eval" || proposal.improvementEval.status === "evaluating")).sort((left2, right2) => (left2.improvementEval?.requestedAt ?? left2.provenance.createdAt).localeCompare(right2.improvementEval?.requestedAt ?? right2.provenance.createdAt) || left2.id.localeCompare(right2.id));
32038
+ const pending = snapshot.repoProposals.filter((proposal) => proposal.reviewState === "accepted" && proposal.improvementEval?.requested === true && (proposal.improvementEval.status === "awaiting-eval" || options.recoverEvaluating !== false && proposal.improvementEval.status === "evaluating") && (options.target === undefined || proposal.id === options.target.proposalId && proposal.projectKey === options.target.projectKey)).sort((left2, right2) => (left2.improvementEval?.requestedAt ?? left2.provenance.createdAt).localeCompare(right2.improvementEval?.requestedAt ?? right2.provenance.createdAt) || left2.id.localeCompare(right2.id));
31842
32039
  const result = {
31843
32040
  pending: pending.length,
31844
32041
  attempted: 0,
@@ -31852,6 +32049,7 @@ async function runPendingImprovementEvaluations(options) {
31852
32049
  const limit = Math.max(0, Math.min(options.limit ?? DEFAULT_EVAL_LIMIT, DEFAULT_EVAL_LIMIT));
31853
32050
  for (const proposal of pending.slice(0, limit)) {
31854
32051
  const startedAt = Date.now();
32052
+ let evaluationStarted = false;
31855
32053
  let modelAttempted = false;
31856
32054
  let evidenceRef = proposal.provenance.sourceRefs[0] ?? null;
31857
32055
  result.attempted += 1;
@@ -31866,6 +32064,7 @@ async function runPendingImprovementEvaluations(options) {
31866
32064
  evidenceRef,
31867
32065
  now: now(options)
31868
32066
  });
32067
+ evaluationStarted = true;
31869
32068
  const evidence = await loadImprovementEvalEvidence(options.homeDir, proposal);
31870
32069
  evidenceRef = evidence.evidenceRef;
31871
32070
  if (evidence.inconclusiveReason !== null) {
@@ -31914,28 +32113,33 @@ async function runPendingImprovementEvaluations(options) {
31914
32113
  } catch (error) {
31915
32114
  const message = safeError(error);
31916
32115
  result.warnings.push(`${proposal.id}: ${message}`);
31917
- const persisted = await persistCompletedEvaluation({
31918
- options,
31919
- proposal,
31920
- status: "inconclusive",
31921
- evidenceRef,
31922
- result: {
31923
- ...createLocalInconclusiveResult(`The bounded replay could not complete: ${message}`, Date.now() - startedAt),
31924
- evaluatedBy: modelAttempted ? "local-code-agent" : "local-eval-orchestrator",
31925
- metrics: {
31926
- durationMs: Date.now() - startedAt,
31927
- modelCalls: modelAttempted ? 1 : 0,
31928
- inputTokens: null,
31929
- outputTokens: null,
31930
- toolCalls: null
32116
+ if (evaluationStarted) {
32117
+ const persisted = await persistCompletedEvaluation({
32118
+ options,
32119
+ proposal,
32120
+ status: "inconclusive",
32121
+ evidenceRef,
32122
+ result: {
32123
+ ...createLocalInconclusiveResult(`The bounded replay could not complete: ${message}`, Date.now() - startedAt),
32124
+ evaluatedBy: modelAttempted ? "local-code-agent" : "local-eval-orchestrator",
32125
+ metrics: {
32126
+ durationMs: Date.now() - startedAt,
32127
+ modelCalls: modelAttempted ? 1 : 0,
32128
+ inputTokens: null,
32129
+ outputTokens: null,
32130
+ toolCalls: null
32131
+ }
31931
32132
  }
31932
- }
31933
- }).catch((persistError) => {
31934
- result.warnings.push(`${proposal.id}: ${safeError(persistError)}`);
31935
- return null;
31936
- });
31937
- if (persisted !== null)
31938
- result.inconclusive += 1;
32133
+ }).catch((persistError) => {
32134
+ result.warnings.push(`${proposal.id}: ${safeError(persistError)}`);
32135
+ return null;
32136
+ });
32137
+ if (persisted !== null)
32138
+ result.inconclusive += 1;
32139
+ } else {
32140
+ result.attempted -= 1;
32141
+ result.proposalIds.pop();
32142
+ }
31939
32143
  }
31940
32144
  await reportProgress2(options, result, "updated");
31941
32145
  }
@@ -31975,7 +32179,7 @@ function classifyImprovementEvalAnalysis(analysis3) {
31975
32179
  }
31976
32180
  function createCodexImprovementEvalAnalyzer(input = {}) {
31977
32181
  return async (candidate) => {
31978
- const workDir = await mkdtemp2(join32(tmpdir2(), "evodev-improvement-eval-"));
32182
+ const workDir = await mkdtemp2(join32(tmpdir3(), "evodev-improvement-eval-"));
31979
32183
  const schemaPath = join32(workDir, "output.schema.json");
31980
32184
  const outputPath = join32(workDir, "output.json");
31981
32185
  try {
@@ -32033,7 +32237,7 @@ async function loadImprovementEvalEvidence(homeDir, proposal) {
32033
32237
  inconclusiveReason: "The accepted proposal target repository is unavailable."
32034
32238
  };
32035
32239
  }
32036
- const repoRoot = await realpath4(proposal.targetRepoPath).catch(() => null);
32240
+ const repoRoot = await realpath5(proposal.targetRepoPath).catch(() => null);
32037
32241
  if (repoRoot === null || !(await stat21(repoRoot).catch(() => null))?.isDirectory()) {
32038
32242
  return {
32039
32243
  repoRoot: "",
@@ -32307,9 +32511,9 @@ var IMPROVEMENT_EVAL_SCHEMA = {
32307
32511
  // packages/cli/src/session-proposals.ts
32308
32512
  import { spawn as spawn5 } from "node:child_process";
32309
32513
  import { createHash as createHash8, randomUUID as randomUUID4 } from "node:crypto";
32310
- import { mkdir as mkdir21, mkdtemp as mkdtemp3, open as open5, readFile as readFile32, realpath as realpath5, rm as rm10, stat as stat22, writeFile as writeFile21 } from "node:fs/promises";
32311
- import { tmpdir as tmpdir3 } from "node:os";
32312
- import { basename as basename7, dirname as dirname25, isAbsolute as isAbsolute11, join as join33, relative as relative12, resolve as resolve11 } from "node:path";
32514
+ import { mkdir as mkdir21, mkdtemp as mkdtemp3, open as open5, readFile as readFile32, realpath as realpath6, rm as rm10, stat as stat22, writeFile as writeFile21 } from "node:fs/promises";
32515
+ import { tmpdir as tmpdir4 } from "node:os";
32516
+ import { basename as basename7, dirname as dirname25, isAbsolute as isAbsolute12, join as join33, relative as relative13, resolve as resolve12 } from "node:path";
32313
32517
  var ELIGIBLE_REASONS = new Set(["user-interruption", "intent-refinement"]);
32314
32518
  var DEFAULT_SCAN_LIMIT = 5;
32315
32519
  var MODEL_TIMEOUT_MS3 = 3 * 60 * 1000;
@@ -32578,7 +32782,7 @@ async function readDailySessionProposalState(homeDir) {
32578
32782
  }
32579
32783
  function createCodexSessionProposalAnalyzer(input = {}) {
32580
32784
  return async (evidence) => {
32581
- const workDir = await mkdtemp3(join33(tmpdir3(), "evodev-session-proposal-"));
32785
+ const workDir = await mkdtemp3(join33(tmpdir4(), "evodev-session-proposal-"));
32582
32786
  const schemaPath = join33(workDir, "output.schema.json");
32583
32787
  const outputPath = join33(workDir, "output.json");
32584
32788
  try {
@@ -32650,8 +32854,8 @@ async function resolveSegmentRepoRoot2(events, homeDir) {
32650
32854
  for (const event of [...events].reverse()) {
32651
32855
  if (!isRecord19(event.payload) || typeof event.payload.cwd !== "string")
32652
32856
  continue;
32653
- const cwd = await realpath5(event.payload.cwd).catch(() => null);
32654
- if (cwd === null || cwd === resolve11(homeDir))
32857
+ const cwd = await realpath6(event.payload.cwd).catch(() => null);
32858
+ if (cwd === null || cwd === resolve12(homeDir))
32655
32859
  continue;
32656
32860
  if (!await isDirectory4(cwd))
32657
32861
  continue;
@@ -32661,7 +32865,7 @@ async function resolveSegmentRepoRoot2(events, homeDir) {
32661
32865
  }
32662
32866
  async function findRepositoryRoot2(cwd, homeDir) {
32663
32867
  let current = cwd;
32664
- const stop = resolve11(homeDir);
32868
+ const stop = resolve12(homeDir);
32665
32869
  while (current !== dirname25(current) && current !== stop) {
32666
32870
  if (await pathExists10(join33(current, ".git")))
32667
32871
  return current;
@@ -32673,9 +32877,9 @@ async function validatePlannedFiles(repoRoot, files) {
32673
32877
  if (files.length === 0)
32674
32878
  throw new Error("Repo proposal contains no planned files.");
32675
32879
  for (const file of files) {
32676
- const target = resolve11(repoRoot, file.relativePath);
32677
- const child = relative12(repoRoot, target);
32678
- if (child === "" || child.startsWith("..") || isAbsolute11(child)) {
32880
+ const target = resolve12(repoRoot, file.relativePath);
32881
+ const child = relative13(repoRoot, target);
32882
+ if (child === "" || child.startsWith("..") || isAbsolute12(child)) {
32679
32883
  throw new Error(`Repo proposal path escapes the target repository: ${file.relativePath}`);
32680
32884
  }
32681
32885
  if (!isGovernanceAssetPath(child)) {
@@ -33217,7 +33421,7 @@ class EvolutionJobService {
33217
33421
  } else {
33218
33422
  job = updateStage(job, "improvement-evals", {
33219
33423
  status: "skipped",
33220
- detail: input.source === "schedule" ? "Scheduled evolution never runs improvement evaluation." : "Improvement evaluation was not requested."
33424
+ detail: input.source === "schedule" ? "Scheduled evolution never runs improvement evaluation." : "Repo proposal evaluation runs directly from Approve & Apply."
33221
33425
  });
33222
33426
  }
33223
33427
  await persist();
@@ -33309,7 +33513,7 @@ function createEvolutionJob(input, now2) {
33309
33513
  stages: [
33310
33514
  createStage("knowledge", "Semantic knowledge distillation"),
33311
33515
  createStage("recommendations", "Skill and rule recommendations"),
33312
- createStage("improvement-evals", "Accepted proposal evaluation"),
33516
+ createStage("improvement-evals", "Repo proposal application"),
33313
33517
  createStage("finalize", "Evolution refresh")
33314
33518
  ],
33315
33519
  warnings: [],
@@ -33477,7 +33681,7 @@ import {
33477
33681
  access as access2,
33478
33682
  chmod as chmod3,
33479
33683
  mkdir as mkdir23,
33480
- realpath as realpath6,
33684
+ realpath as realpath7,
33481
33685
  rename as rename5,
33482
33686
  rm as rm12,
33483
33687
  stat as stat24,
@@ -33564,7 +33768,7 @@ class OsEvolutionScheduler {
33564
33768
  if (candidate === undefined || candidate.trim() === "") {
33565
33769
  throw new Error("Cannot resolve the evodev executable for scheduler installation.");
33566
33770
  }
33567
- const resolved = await realpath6(candidate).catch(() => candidate);
33771
+ const resolved = await realpath7(candidate).catch(() => candidate);
33568
33772
  if (!await isFile3(resolved)) {
33569
33773
  throw new Error(`EvoDev scheduler executable is not a file: ${resolved}`);
33570
33774
  }
@@ -34401,10 +34605,10 @@ function shouldAttachAfterStart(flags, options) {
34401
34605
 
34402
34606
  class TmuxAttachRunner {
34403
34607
  async attachSession(session) {
34404
- return new Promise((resolve12, reject) => {
34608
+ return new Promise((resolve13, reject) => {
34405
34609
  const child = spawn7("tmux", ["-u", "attach-session", "-t", session], { stdio: "inherit" });
34406
34610
  child.on("error", reject);
34407
- child.on("close", (code) => resolve12(code ?? 0));
34611
+ child.on("close", (code) => resolve13(code ?? 0));
34408
34612
  });
34409
34613
  }
34410
34614
  }
@@ -34832,8 +35036,8 @@ import { createServer as createServer3 } from "node:http";
34832
35036
  // packages/cli/src/proposal-execution.ts
34833
35037
  import { spawn as spawn8 } from "node:child_process";
34834
35038
  import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
34835
- import { mkdir as mkdir24, readFile as readFile35, readdir as readdir18, realpath as realpath7, rename as rename6, rm as rm13, stat as stat25, writeFile as writeFile24 } from "node:fs/promises";
34836
- import { dirname as dirname28, isAbsolute as isAbsolute12, join as join36, relative as relative13, resolve as resolve12 } from "node:path";
35039
+ import { mkdir as mkdir24, readFile as readFile35, readdir as readdir18, realpath as realpath8, rename as rename6, rm as rm13, stat as stat25, writeFile as writeFile24 } from "node:fs/promises";
35040
+ import { dirname as dirname28, isAbsolute as isAbsolute13, join as join36, relative as relative14, resolve as resolve13 } from "node:path";
34837
35041
  var MAX_COMMAND_OUTPUT_BYTES = 1024 * 1024;
34838
35042
  var MAX_DIRTY_FILES_IN_RESPONSE = 20;
34839
35043
  var MAX_CHANGED_FILES_IN_RECORD = 100;
@@ -34886,7 +35090,7 @@ class RepoProposalExecutionService {
34886
35090
  }).catch(() => null);
34887
35091
  return {
34888
35092
  proposal: summarizeProposal(proposal),
34889
- repository: repository === null ? null : summarizeRepositoryState(repository),
35093
+ repository: repository === null ? null : summarizeRepositoryState(proposal, repository),
34890
35094
  repositoryError,
34891
35095
  agents: agents2,
34892
35096
  latestExecution,
@@ -34903,8 +35107,8 @@ class RepoProposalExecutionService {
34903
35107
  if (repository.fingerprint !== input.expectedFingerprint) {
34904
35108
  throw new ProposalExecutionError("conflict", "Repository state changed after confirmation. Refresh and confirm the latest state.");
34905
35109
  }
34906
- if (repository.dirty && input.confirmDirty !== true) {
34907
- throw new ProposalExecutionError("confirmation-required", "Uncommitted repository changes require explicit confirmation.");
35110
+ if (requiresDirtyConfirmation(proposal, repository) && input.confirmDirty !== true) {
35111
+ throw new ProposalExecutionError("confirmation-required", "Uncommitted changes overlap the reviewed proposal files and require explicit confirmation.");
34908
35112
  }
34909
35113
  const agents2 = await discoverExecutionAgents(this.commandRunner);
34910
35114
  if (!agents2.some((agent) => agent.id === input.agent && agent.installed)) {
@@ -34914,8 +35118,8 @@ class RepoProposalExecutionService {
34914
35118
  if (repository.fingerprint !== input.expectedFingerprint) {
34915
35119
  throw new ProposalExecutionError("conflict", "Repository state changed while the Code Agent was being checked. Refresh and confirm the latest state.");
34916
35120
  }
34917
- if (repository.dirty && input.confirmDirty !== true) {
34918
- throw new ProposalExecutionError("confirmation-required", "Uncommitted repository changes require explicit confirmation.");
35121
+ if (requiresDirtyConfirmation(proposal, repository) && input.confirmDirty !== true) {
35122
+ throw new ProposalExecutionError("confirmation-required", "Uncommitted changes overlap the reviewed proposal files and require explicit confirmation.");
34919
35123
  }
34920
35124
  const latestExecution = await readLatestRepoProposalExecution({
34921
35125
  homeDir: input.homeDir,
@@ -34939,7 +35143,7 @@ class RepoProposalExecutionService {
34939
35143
  head: repository.head,
34940
35144
  dirtyAtStart: repository.dirty,
34941
35145
  dirtyFileCountAtStart: repository.entries.length,
34942
- dirtyConfirmation: repository.dirty ? "confirmed" : "not-required",
35146
+ dirtyConfirmation: !repository.dirty ? "not-required" : requiresDirtyConfirmation(proposal, repository) ? "confirmed" : "approval-covered",
34943
35147
  changedFiles: [],
34944
35148
  changedFilesTruncated: false,
34945
35149
  startedAt: this.now(),
@@ -35218,7 +35422,7 @@ async function readRepositoryState(proposal, runner) {
35218
35422
  }
35219
35423
  let repoRoot;
35220
35424
  try {
35221
- repoRoot = await realpath7(proposal.targetRepoPath);
35425
+ repoRoot = await realpath8(proposal.targetRepoPath);
35222
35426
  if (!(await stat25(repoRoot)).isDirectory())
35223
35427
  throw new Error("not a directory");
35224
35428
  } catch {
@@ -35228,7 +35432,7 @@ async function readRepositoryState(proposal, runner) {
35228
35432
  if (topLevel.exitCode !== 0) {
35229
35433
  throw new ProposalExecutionError("invalid", "Target path is not a git repository.");
35230
35434
  }
35231
- const resolvedTopLevel = await realpath7(topLevel.stdout.trim());
35435
+ const resolvedTopLevel = await realpath8(topLevel.stdout.trim());
35232
35436
  if (resolvedTopLevel !== repoRoot) {
35233
35437
  throw new ProposalExecutionError("invalid", "Proposal target must be the git repository root.");
35234
35438
  }
@@ -35303,9 +35507,9 @@ function sanitizeGitPath(path2) {
35303
35507
  return normalized;
35304
35508
  }
35305
35509
  async function pathMetadataSignature(repoRoot, path2) {
35306
- const target = resolve12(repoRoot, path2);
35307
- const child = relative13(repoRoot, target);
35308
- if (child === "" || child.startsWith("..") || isAbsolute12(child))
35510
+ const target = resolve13(repoRoot, path2);
35511
+ const child = relative14(repoRoot, target);
35512
+ if (child === "" || child.startsWith("..") || isAbsolute13(child))
35309
35513
  return "outside";
35310
35514
  return await fileMetadataSignature(target);
35311
35515
  }
@@ -35319,8 +35523,10 @@ async function fileMetadataSignature(path2) {
35319
35523
  throw error;
35320
35524
  }
35321
35525
  }
35322
- function summarizeRepositoryState(repository) {
35526
+ function summarizeRepositoryState(proposal, repository) {
35323
35527
  const dirtyFiles = repository.entries.slice(0, MAX_DIRTY_FILES_IN_RESPONSE).map((entry) => entry.path);
35528
+ const conflicts = dirtyPlannedFileConflicts(proposal, repository);
35529
+ const dirtyConflictFiles = conflicts.slice(0, MAX_DIRTY_FILES_IN_RESPONSE);
35324
35530
  return {
35325
35531
  branch: repository.branch,
35326
35532
  head: repository.head,
@@ -35328,9 +35534,19 @@ function summarizeRepositoryState(repository) {
35328
35534
  dirtyFileCount: repository.entries.length,
35329
35535
  dirtyFiles,
35330
35536
  dirtyFilesTruncated: repository.entries.length > dirtyFiles.length,
35537
+ requiresDirtyConfirmation: conflicts.length > 0,
35538
+ dirtyConflictFiles,
35539
+ dirtyConflictFilesTruncated: conflicts.length > dirtyConflictFiles.length,
35331
35540
  fingerprint: repository.fingerprint
35332
35541
  };
35333
35542
  }
35543
+ function requiresDirtyConfirmation(proposal, repository) {
35544
+ return dirtyPlannedFileConflicts(proposal, repository).length > 0;
35545
+ }
35546
+ function dirtyPlannedFileConflicts(proposal, repository) {
35547
+ const plannedFiles = new Set(proposal.plannedFiles.map((file) => file.relativePath.replaceAll("\\", "/")));
35548
+ return [...new Set(repository.entries.map((entry) => entry.path))].filter((path2) => plannedFiles.has(path2)).sort();
35549
+ }
35334
35550
  function summarizeProposal(proposal) {
35335
35551
  if (proposal.targetRepoPath === null)
35336
35552
  throw new ProposalExecutionError("invalid", "Missing target.");
@@ -35384,7 +35600,7 @@ function resolveRepoProposalExecutionPolicy(proposal, confirmInconclusiveEval =
35384
35600
  return {
35385
35601
  canExecute: false,
35386
35602
  requiresInconclusiveConfirmation: false,
35387
- message: evaluation.status === "evaluating" ? "The improvement evaluation is still running." : "This proposal is awaiting improvement evaluation. Long-press Evolve to evaluate it."
35603
+ message: evaluation.status === "evaluating" ? "The improvement evaluation is still running." : "This proposal is awaiting improvement evaluation. Use Evaluate & Apply to continue it in this checkout."
35388
35604
  };
35389
35605
  }
35390
35606
  function buildRepoProposalExecutionArgs(agent, repoRoot) {
@@ -35412,7 +35628,8 @@ function buildRepoProposalExecutionArgs(agent, repoRoot) {
35412
35628
  ];
35413
35629
  }
35414
35630
  function buildRepoProposalExecutionPrompt(input) {
35415
- const dirtyAuthorization = input.repository.dirty ? `The server detected ${input.repository.entries.length} uncommitted path(s), and the user explicitly confirmed execution on that dirty working tree.` : "The server detected a clean working tree; no dirty-worktree confirmation was required.";
35631
+ const conflicts = dirtyPlannedFileConflicts(input.proposal, input.repository);
35632
+ const dirtyAuthorization = !input.repository.dirty ? "The server detected a clean working tree; no dirty-worktree confirmation was required." : conflicts.length > 0 ? `The server detected ${input.repository.entries.length} uncommitted path(s), including ${conflicts.length} reviewed target-file conflict(s), and the user explicitly confirmed execution on that dirty working tree.` : `The server detected ${input.repository.entries.length} non-overlapping uncommitted path(s). Approval authorizes execution while preserving those existing changes; the repository fingerprint must remain unchanged before launch.`;
35416
35633
  const changes = input.proposal.plannedFiles.map((file, index) => `${index + 1}. ${file.action} ${file.relativePath}
35417
35634
  Reason: ${file.reason}
35418
35635
  Requested change:
@@ -35563,7 +35780,7 @@ function parseExecutionRecord(value) {
35563
35780
  "spawn-failed",
35564
35781
  "state-conflict"
35565
35782
  ]);
35566
- if (Object.keys(value).some((key) => !allowedKeys.has(key)) || value.schemaVersion !== 1 || value.kind !== "repo-proposal-execution" || !isSafeRecordIdentity(value.id) || !isSafeRecordIdentity(value.proposalId) || !isSafeRecordIdentity(value.projectKey) || !isSafeRecordIdentity(value.runId) || value.agent !== "codex" && value.agent !== "claude" || !["running", "succeeded", "failed"].includes(String(value.status)) || value.branch !== null && typeof value.branch !== "string" || typeof value.head !== "string" || !/^[a-f0-9]{40,64}$/u.test(value.head) || typeof value.dirtyAtStart !== "boolean" || !Number.isInteger(value.dirtyFileCountAtStart) || Number(value.dirtyFileCountAtStart) < 0 || value.dirtyConfirmation !== "not-required" && value.dirtyConfirmation !== "confirmed" || !Array.isArray(value.changedFiles) || value.changedFiles.length > MAX_CHANGED_FILES_IN_RECORD || !value.changedFiles.every((path2) => typeof path2 === "string") || typeof value.changedFilesTruncated !== "boolean" || typeof value.startedAt !== "string" || !isIsoTimestamp(value.startedAt) || value.finishedAt !== null && (typeof value.finishedAt !== "string" || !isIsoTimestamp(value.finishedAt)) || value.pid !== null && (!Number.isInteger(value.pid) || Number(value.pid) <= 0) || value.exitCode !== null && !Number.isInteger(value.exitCode) || value.signal !== null && typeof value.signal !== "string" || value.errorCode !== null && (typeof value.errorCode !== "string" || !errorCodes.has(value.errorCode)) || value.metadataOnly !== true || value.rawPromptStored !== false || value.sourceContentStored !== false || value.rawCommandOutputStored !== false) {
35783
+ if (Object.keys(value).some((key) => !allowedKeys.has(key)) || value.schemaVersion !== 1 || value.kind !== "repo-proposal-execution" || !isSafeRecordIdentity(value.id) || !isSafeRecordIdentity(value.proposalId) || !isSafeRecordIdentity(value.projectKey) || !isSafeRecordIdentity(value.runId) || value.agent !== "codex" && value.agent !== "claude" || !["running", "succeeded", "failed"].includes(String(value.status)) || value.branch !== null && typeof value.branch !== "string" || typeof value.head !== "string" || !/^[a-f0-9]{40,64}$/u.test(value.head) || typeof value.dirtyAtStart !== "boolean" || !Number.isInteger(value.dirtyFileCountAtStart) || Number(value.dirtyFileCountAtStart) < 0 || value.dirtyConfirmation !== "not-required" && value.dirtyConfirmation !== "approval-covered" && value.dirtyConfirmation !== "confirmed" || !Array.isArray(value.changedFiles) || value.changedFiles.length > MAX_CHANGED_FILES_IN_RECORD || !value.changedFiles.every((path2) => typeof path2 === "string") || typeof value.changedFilesTruncated !== "boolean" || typeof value.startedAt !== "string" || !isIsoTimestamp(value.startedAt) || value.finishedAt !== null && (typeof value.finishedAt !== "string" || !isIsoTimestamp(value.finishedAt)) || value.pid !== null && (!Number.isInteger(value.pid) || Number(value.pid) <= 0) || value.exitCode !== null && !Number.isInteger(value.exitCode) || value.signal !== null && typeof value.signal !== "string" || value.errorCode !== null && (typeof value.errorCode !== "string" || !errorCodes.has(value.errorCode)) || value.metadataOnly !== true || value.rawPromptStored !== false || value.sourceContentStored !== false || value.rawCommandOutputStored !== false) {
35567
35784
  throw new Error("Execution record is invalid.");
35568
35785
  }
35569
35786
  return value;
@@ -35617,6 +35834,115 @@ function isAlreadyExistsError3(error) {
35617
35834
  return error instanceof Error && "code" in error && error.code === "EEXIST";
35618
35835
  }
35619
35836
 
35837
+ // packages/cli/src/proposal-application.ts
35838
+ async function runRepoProposalApplication(input, dependencies) {
35839
+ const initial = await dependencies.proposalExecution.prepare({
35840
+ homeDir: input.homeDir,
35841
+ proposalId: input.proposalId,
35842
+ projectKey: input.projectKey
35843
+ });
35844
+ assertApplicationPreflight(initial, input);
35845
+ let reviewChanged = false;
35846
+ if (initial.proposal.reviewState !== "accepted") {
35847
+ const decision = await (dependencies.applyDecision ?? applyReviewDecision)({
35848
+ homeDir: input.homeDir,
35849
+ kind: "repo-proposal",
35850
+ id: input.proposalId,
35851
+ projectKey: input.projectKey,
35852
+ decision: "accepted",
35853
+ expectedState: input.expectedReviewState,
35854
+ improvementEvalRequested: input.improvementEvalRequested
35855
+ });
35856
+ reviewChanged = decision.changed;
35857
+ } else if (input.improvementEvalRequested !== undefined) {
35858
+ throw new ProposalExecutionError("invalid", "An accepted proposal cannot change its improvement-evaluation selection.");
35859
+ }
35860
+ let evaluation = null;
35861
+ let current = await dependencies.proposalExecution.prepare({
35862
+ homeDir: input.homeDir,
35863
+ proposalId: input.proposalId,
35864
+ projectKey: input.projectKey
35865
+ });
35866
+ const evaluationStatus = current.proposal.improvementEval?.status;
35867
+ if (evaluationStatus === "evaluating") {
35868
+ return blockedResult(reviewChanged, null, "This proposal is already being evaluated. Its application will remain paused until the evaluation finishes.");
35869
+ }
35870
+ if (evaluationStatus === "awaiting-eval") {
35871
+ try {
35872
+ evaluation = await (dependencies.evaluateImprovements ?? runPendingImprovementEvaluations)({
35873
+ homeDir: input.homeDir,
35874
+ limit: 1,
35875
+ target: {
35876
+ proposalId: current.proposal.id,
35877
+ projectKey: current.proposal.projectKey
35878
+ },
35879
+ recoverEvaluating: false
35880
+ });
35881
+ } catch (error) {
35882
+ return blockedResult(reviewChanged, null, `The proposal was approved, but its improvement evaluation could not start: ${safeError4(error)}`);
35883
+ }
35884
+ current = await dependencies.proposalExecution.prepare({
35885
+ homeDir: input.homeDir,
35886
+ proposalId: input.proposalId,
35887
+ projectKey: input.projectKey
35888
+ });
35889
+ }
35890
+ if (!current.executionPolicy.canExecute) {
35891
+ return blockedResult(reviewChanged, evaluation, current.executionPolicy.message ?? "The approved proposal is not ready to apply.");
35892
+ }
35893
+ try {
35894
+ const execution = await dependencies.proposalExecution.start({
35895
+ homeDir: input.homeDir,
35896
+ proposalId: input.proposalId,
35897
+ projectKey: input.projectKey,
35898
+ agent: input.agent,
35899
+ expectedFingerprint: input.expectedFingerprint,
35900
+ confirmDirty: input.confirmDirty,
35901
+ confirmInconclusiveEval: false
35902
+ });
35903
+ return {
35904
+ status: "executing",
35905
+ reviewChanged,
35906
+ evaluation,
35907
+ execution,
35908
+ message: null
35909
+ };
35910
+ } catch (error) {
35911
+ return blockedResult(reviewChanged, evaluation, error instanceof ProposalExecutionError ? error.message : `The proposal was approved, but repository execution could not start: ${safeError4(error)}`);
35912
+ }
35913
+ }
35914
+ function assertApplicationPreflight(preparation, input) {
35915
+ if (preparation.proposal.reviewState !== input.expectedReviewState) {
35916
+ throw new ProposalExecutionError("conflict", "Review state changed after the page loaded. Refresh and review the latest state.");
35917
+ }
35918
+ const repository = preparation.repository;
35919
+ if (repository === null) {
35920
+ throw new ProposalExecutionError("unavailable", preparation.repositoryError ?? "The target repository is unavailable.");
35921
+ }
35922
+ if (repository.fingerprint !== input.expectedFingerprint) {
35923
+ throw new ProposalExecutionError("conflict", "Repository state changed after confirmation. Refresh and confirm the latest state.");
35924
+ }
35925
+ if (repository.requiresDirtyConfirmation && input.confirmDirty !== true) {
35926
+ throw new ProposalExecutionError("confirmation-required", "Uncommitted changes overlap the reviewed proposal files and require explicit confirmation.");
35927
+ }
35928
+ if (!preparation.agents.some((agent) => agent.id === input.agent && agent.installed)) {
35929
+ throw new ProposalExecutionError("unavailable", `${input.agent === "codex" ? "Codex" : "Claude Code"} is not available on PATH.`);
35930
+ }
35931
+ }
35932
+ function blockedResult(reviewChanged, evaluation, message) {
35933
+ return {
35934
+ status: "blocked",
35935
+ reviewChanged,
35936
+ evaluation,
35937
+ execution: null,
35938
+ message
35939
+ };
35940
+ }
35941
+ function safeError4(error) {
35942
+ const message = error instanceof Error ? error.message : String(error);
35943
+ return message.replaceAll(/\s+/gu, " ").trim().slice(0, 300) || "unknown error";
35944
+ }
35945
+
35620
35946
  // packages/cli/src/ui/assets.ts
35621
35947
  import { readFile as readFile36 } from "node:fs/promises";
35622
35948
  import { dirname as dirname29, join as join37 } from "node:path";
@@ -35679,6 +36005,215 @@ async function fileExists2(path2) {
35679
36005
  }
35680
36006
  }
35681
36007
 
36008
+ // packages/cli/src/ui/process-runtime.ts
36009
+ import { execFile as execFile2 } from "node:child_process";
36010
+ import { createConnection } from "node:net";
36011
+ import { promisify as promisify2 } from "node:util";
36012
+ var execFileAsync2 = promisify2(execFile2);
36013
+ var MAX_PROCESS_LIST_BYTES = 2 * 1024 * 1024;
36014
+ var PORT_PROBE_TIMEOUT_MS = 500;
36015
+
36016
+ class NodeUiProcessController {
36017
+ currentUid = typeof process.getuid === "function" && Number.isInteger(process.getuid()) ? process.getuid() : null;
36018
+ async listProcesses() {
36019
+ if (this.currentUid === null || process.platform === "win32")
36020
+ return [];
36021
+ try {
36022
+ const result = await execFileAsync2("ps", ["-axo", "pid=,uid=,command="], {
36023
+ encoding: "utf8",
36024
+ maxBuffer: MAX_PROCESS_LIST_BYTES
36025
+ });
36026
+ return parseProcessList(result.stdout);
36027
+ } catch {
36028
+ return [];
36029
+ }
36030
+ }
36031
+ async signal(pid, signal) {
36032
+ process.kill(pid, signal);
36033
+ }
36034
+ async isAlive(pid) {
36035
+ try {
36036
+ process.kill(pid, 0);
36037
+ return true;
36038
+ } catch (error) {
36039
+ if (isNodeError3(error) && error.code === "ESRCH")
36040
+ return false;
36041
+ throw error;
36042
+ }
36043
+ }
36044
+ async ownsListeningPort(pid, port) {
36045
+ if (process.platform === "win32")
36046
+ return null;
36047
+ try {
36048
+ const result = await execFileAsync2("lsof", ["-nP", "-a", "-p", String(pid), `-iTCP:${port}`, "-sTCP:LISTEN", "-Fp"], {
36049
+ encoding: "utf8",
36050
+ maxBuffer: 64 * 1024
36051
+ });
36052
+ return result.stdout.split(`
36053
+ `).some((line) => line === `p${pid}`);
36054
+ } catch (error) {
36055
+ if (isNodeError3(error) && error.code === "ENOENT")
36056
+ return null;
36057
+ if (isExecFileExitError(error) && error.code === 1)
36058
+ return false;
36059
+ return null;
36060
+ }
36061
+ }
36062
+ async isPortOccupied(host, port) {
36063
+ return await new Promise((resolve14) => {
36064
+ let settled = false;
36065
+ const socket = createConnection({ host, port });
36066
+ const finish = (occupied) => {
36067
+ if (settled)
36068
+ return;
36069
+ settled = true;
36070
+ socket.destroy();
36071
+ resolve14(occupied);
36072
+ };
36073
+ socket.setTimeout(PORT_PROBE_TIMEOUT_MS);
36074
+ socket.once("connect", () => finish(true));
36075
+ socket.once("timeout", () => finish(true));
36076
+ socket.once("error", (error) => {
36077
+ finish(error.code !== "ECONNREFUSED");
36078
+ });
36079
+ });
36080
+ }
36081
+ }
36082
+ function parseUiServerProcess(input) {
36083
+ if (input.currentUid === null || input.snapshot.uid !== input.currentUid || input.snapshot.pid === (input.currentPid ?? process.pid)) {
36084
+ return null;
36085
+ }
36086
+ const tokens = splitCommandLine(input.snapshot.command);
36087
+ if (tokens === null)
36088
+ return null;
36089
+ const uiIndex = tokens.indexOf("ui");
36090
+ if (uiIndex < 1 || !isEvoDevInvocation(tokens, uiIndex))
36091
+ return null;
36092
+ let host = "127.0.0.1";
36093
+ let port = input.defaultPort;
36094
+ let hostSeen = false;
36095
+ let portSeen = false;
36096
+ for (let index = uiIndex + 1;index < tokens.length; index += 1) {
36097
+ const token = tokens[index];
36098
+ if (token === "--open" || token === "--no-open" || token === "--no-proposal-scan") {
36099
+ continue;
36100
+ }
36101
+ if (token === "--host") {
36102
+ if (hostSeen)
36103
+ return null;
36104
+ const value = tokens[index + 1];
36105
+ if (value !== "127.0.0.1" && value !== "localhost")
36106
+ return null;
36107
+ host = value;
36108
+ hostSeen = true;
36109
+ index += 1;
36110
+ continue;
36111
+ }
36112
+ if (token === "--port") {
36113
+ if (portSeen)
36114
+ return null;
36115
+ const value = Number(tokens[index + 1]);
36116
+ if (!Number.isInteger(value) || value < 1 || value > 65535)
36117
+ return null;
36118
+ port = value;
36119
+ portSeen = true;
36120
+ index += 1;
36121
+ continue;
36122
+ }
36123
+ return null;
36124
+ }
36125
+ return { pid: input.snapshot.pid, host, port };
36126
+ }
36127
+ function parseProcessList(value) {
36128
+ const processes = [];
36129
+ for (const line of value.split(`
36130
+ `)) {
36131
+ const match = /^\s*(\d+)\s+(\d+)\s+(.+?)\s*$/u.exec(line);
36132
+ if (match === null)
36133
+ continue;
36134
+ const pid = Number(match[1]);
36135
+ const uid = Number(match[2]);
36136
+ const command = match[3] ?? "";
36137
+ if (!Number.isSafeInteger(pid) || pid < 1 || !Number.isSafeInteger(uid) || uid < 0)
36138
+ continue;
36139
+ processes.push({ pid, uid, command });
36140
+ }
36141
+ return processes;
36142
+ }
36143
+ function isEvoDevInvocation(tokens, uiIndex) {
36144
+ if (uiIndex === 1)
36145
+ return isEvoDevEntrypoint(tokens[0] ?? "");
36146
+ if (uiIndex !== 2 || !isNodeLauncher(tokens[0] ?? ""))
36147
+ return false;
36148
+ return isEvoDevEntrypoint(tokens[1] ?? "");
36149
+ }
36150
+ function isNodeLauncher(value) {
36151
+ const name = portableBasename(value);
36152
+ return name === "node" || name === "node.exe" || name === "bun" || name === "bun.exe";
36153
+ }
36154
+ function isEvoDevEntrypoint(value) {
36155
+ const normalized = value.replaceAll("\\", "/");
36156
+ const name = portableBasename(normalized);
36157
+ if (name === "evodev" || name === "evodev.js")
36158
+ return true;
36159
+ if (normalized.includes("/@evo-dev/evodev/") && (normalized.endsWith("/dist/index.js") || normalized.endsWith("/bin/index.js"))) {
36160
+ return true;
36161
+ }
36162
+ return normalized.includes("/evodev/") && (normalized.endsWith("/packages/cli/src/index.ts") || normalized.endsWith("/packages/cli/dist/index.js"));
36163
+ }
36164
+ function portableBasename(value) {
36165
+ return value.replaceAll("\\", "/").split("/").at(-1)?.toLowerCase() ?? "";
36166
+ }
36167
+ function splitCommandLine(value) {
36168
+ const tokens = [];
36169
+ let current = "";
36170
+ let quote = null;
36171
+ let escaped = false;
36172
+ const pushCurrent = () => {
36173
+ if (current === "")
36174
+ return;
36175
+ tokens.push(current);
36176
+ current = "";
36177
+ };
36178
+ for (const character of value.trim()) {
36179
+ if (escaped) {
36180
+ current += character;
36181
+ escaped = false;
36182
+ continue;
36183
+ }
36184
+ if (character === "\\" && quote !== "'") {
36185
+ escaped = true;
36186
+ continue;
36187
+ }
36188
+ if (quote !== null) {
36189
+ if (character === quote)
36190
+ quote = null;
36191
+ else
36192
+ current += character;
36193
+ continue;
36194
+ }
36195
+ if (character === "'" || character === '"') {
36196
+ quote = character;
36197
+ continue;
36198
+ }
36199
+ if (/\s/u.test(character)) {
36200
+ pushCurrent();
36201
+ continue;
36202
+ }
36203
+ current += character;
36204
+ }
36205
+ if (quote !== null || escaped)
36206
+ return null;
36207
+ pushCurrent();
36208
+ return tokens;
36209
+ }
36210
+ function isNodeError3(error) {
36211
+ return error instanceof Error && "code" in error;
36212
+ }
36213
+ function isExecFileExitError(error) {
36214
+ return error instanceof Error && "code" in error && error.code === 1;
36215
+ }
36216
+
35682
36217
  // packages/cli/src/ui/runtime.ts
35683
36218
  import { mkdir as mkdir25, readFile as readFile37, rm as rm14, writeFile as writeFile25 } from "node:fs/promises";
35684
36219
  import { join as join38 } from "node:path";
@@ -35795,12 +36330,12 @@ async function readOptionalFile(path2) {
35795
36330
  try {
35796
36331
  return await readFile37(path2, "utf8");
35797
36332
  } catch (error) {
35798
- if (isNodeError3(error) && error.code === "ENOENT")
36333
+ if (isNodeError4(error) && error.code === "ENOENT")
35799
36334
  return null;
35800
36335
  throw error;
35801
36336
  }
35802
36337
  }
35803
- function isNodeError3(error) {
36338
+ function isNodeError4(error) {
35804
36339
  return error instanceof Error && "code" in error;
35805
36340
  }
35806
36341
 
@@ -36966,6 +37501,8 @@ function sanitizeText4(value) {
36966
37501
  // packages/cli/src/ui/server.ts
36967
37502
  var DEFAULT_UI_PORT = 37646;
36968
37503
  var UI_RUNTIME_REQUEST_TIMEOUT_MS = 2000;
37504
+ var MAX_ORPHAN_UI_CANDIDATES = 20;
37505
+ var MAX_LEGACY_UI_HTML_BYTES = 128 * 1024;
36969
37506
  var MAX_UI_REQUEST_BODY_BYTES = 8 * 1024;
36970
37507
  var UI_RESPONSE_HEADERS = {
36971
37508
  "cache-control": "no-store",
@@ -36993,13 +37530,13 @@ function getUiHelpText() {
36993
37530
  " evodev ui [--host 127.0.0.1|localhost] [--port <port>] [--no-open]",
36994
37531
  " evodev ui status",
36995
37532
  " evodev ui open",
36996
- " evodev ui stop",
37533
+ " evodev ui stop [--host 127.0.0.1|localhost] [--port <port>]",
36997
37534
  " evodev ui snapshot",
36998
37535
  "",
36999
37536
  "Commands:",
37000
37537
  " status Show the managed dashboard process status",
37001
37538
  " open Open the authenticated URL for the running dashboard",
37002
- " stop Gracefully stop the running dashboard",
37539
+ " stop Gracefully stop the managed or safely verified orphaned dashboard",
37003
37540
  " snapshot Print the local metadata dashboard snapshot as JSON",
37004
37541
  "",
37005
37542
  "Options:",
@@ -37036,7 +37573,8 @@ async function runUiCommand(argv, options = {}) {
37036
37573
  return await runUiStatusCommand({
37037
37574
  homeDir,
37038
37575
  write,
37039
- fetchImpl: options.fetchImpl ?? globalThis.fetch
37576
+ fetchImpl: options.fetchImpl ?? globalThis.fetch,
37577
+ processController: options.processController ?? new NodeUiProcessController
37040
37578
  });
37041
37579
  }
37042
37580
  if (command === "open") {
@@ -37049,11 +37587,12 @@ async function runUiCommand(argv, options = {}) {
37049
37587
  });
37050
37588
  }
37051
37589
  if (command === "stop") {
37052
- requireNoUiSubcommandArgs(command, argv.slice(1));
37053
37590
  return await runUiStopCommand({
37054
37591
  homeDir,
37055
37592
  write,
37056
- fetchImpl: options.fetchImpl ?? globalThis.fetch
37593
+ fetchImpl: options.fetchImpl ?? globalThis.fetch,
37594
+ processController: options.processController ?? new NodeUiProcessController,
37595
+ selector: parseUiStopSelector(argv.slice(1))
37057
37596
  });
37058
37597
  }
37059
37598
  const flags = parseServeFlags(argv);
@@ -37066,12 +37605,30 @@ async function runUiCommand(argv, options = {}) {
37066
37605
  fetchImpl: options.fetchImpl ?? globalThis.fetch,
37067
37606
  openBrowser: options.openBrowser ?? openUiBrowser,
37068
37607
  evolutionJobService: options.evolutionJobService,
37069
- proposalExecutionService: options.proposalExecutionService
37608
+ proposalExecutionService: options.proposalExecutionService,
37609
+ proposalApplicationRunner: options.proposalApplicationRunner
37070
37610
  });
37071
37611
  }
37072
37612
  async function runUiStatusCommand(input) {
37073
37613
  const runtime = await readUiRuntimeBundle(input.homeDir);
37074
37614
  if (runtime.state === null) {
37615
+ const orphaned = await inspectOrphanUiProcesses({
37616
+ fetchImpl: input.fetchImpl,
37617
+ processController: input.processController
37618
+ });
37619
+ if (orphaned.verified.length > 0) {
37620
+ const runtimes = orphaned.verified.slice(0, 3).map((process5) => `pid=${process5.pid} url=${buildUiServerProcessUrl(process5)}`).join(", ");
37621
+ input.write(`EvoDev UI: orphaned ${runtimes} (managed state missing${runtime.token === null ? "" : "; stale authentication state present"}; run evodev ui stop)`);
37622
+ return 0;
37623
+ }
37624
+ if (orphaned.unverified.length > 0) {
37625
+ input.write(`EvoDev UI: unavailable; ${orphaned.unverified.length} same-user EvoDev UI process(es) could not be verified`);
37626
+ return 0;
37627
+ }
37628
+ if (await input.processController.isPortOccupied("127.0.0.1", DEFAULT_UI_PORT)) {
37629
+ input.write(`EvoDev UI: unavailable; 127.0.0.1:${DEFAULT_UI_PORT} is occupied by an unmanaged process`);
37630
+ return 0;
37631
+ }
37075
37632
  input.write(runtime.token === null ? "EvoDev UI: stopped" : "EvoDev UI: stopped (stale authentication state present)");
37076
37633
  return 0;
37077
37634
  }
@@ -37126,19 +37683,44 @@ async function runUiStopCommand(input) {
37126
37683
  homeDir: input.homeDir,
37127
37684
  expectedToken: runtime.token
37128
37685
  });
37129
- input.write("EvoDev UI: stopped; removed stale authentication state.");
37130
- } else {
37131
- input.write("EvoDev UI: already stopped.");
37132
37686
  }
37687
+ const stopped2 = await stopVerifiedOrphanUiProcess({
37688
+ fetchImpl: input.fetchImpl,
37689
+ processController: input.processController,
37690
+ selector: input.selector
37691
+ });
37692
+ if (stopped2 !== null) {
37693
+ input.write(`EvoDev UI: stopped orphaned runtime pid=${stopped2.pid} url=${buildUiServerProcessUrl(stopped2)}.`);
37694
+ return 0;
37695
+ }
37696
+ input.write(runtime.token === null ? "EvoDev UI: already stopped." : "EvoDev UI: stopped; removed stale authentication state.");
37133
37697
  return 0;
37134
37698
  }
37699
+ assertUiStopSelectorMatchesRuntime(input.selector, runtime.state);
37135
37700
  if (runtime.token === null) {
37136
37701
  const probe = await probeUiRuntime({
37137
37702
  state: runtime.state,
37138
37703
  fetchImpl: input.fetchImpl
37139
37704
  });
37140
37705
  if (probe.kind === "running") {
37141
- throw new Error("Cannot safely stop EvoDev UI because its authentication state is missing. Stop the owning process manually.");
37706
+ const stopped2 = await stopVerifiedOrphanUiProcess({
37707
+ fetchImpl: input.fetchImpl,
37708
+ processController: input.processController,
37709
+ selector: {
37710
+ host: runtime.state.host,
37711
+ port: runtime.state.port
37712
+ },
37713
+ expectedPid: runtime.state.pid
37714
+ });
37715
+ if (stopped2 === null) {
37716
+ throw new Error("Cannot safely stop EvoDev UI because its authentication state is missing and the owning process could not be verified.");
37717
+ }
37718
+ await removeUiRuntimeState({
37719
+ homeDir: input.homeDir,
37720
+ expectedInstanceId: runtime.state.instanceId
37721
+ });
37722
+ input.write("EvoDev UI: stopped; recovered missing authentication state.");
37723
+ return 0;
37142
37724
  }
37143
37725
  if (probe.kind === "unavailable" && probe.timedOut) {
37144
37726
  throw new Error("Cannot safely stop EvoDev UI because its health check timed out.");
@@ -37265,18 +37847,165 @@ async function waitForUiRuntimeRemoval(input) {
37265
37847
  }
37266
37848
  return false;
37267
37849
  }
37850
+ async function stopVerifiedOrphanUiProcess(input) {
37851
+ const inspection = await inspectOrphanUiProcesses(input);
37852
+ const verified = inspection.verified.filter((candidate2) => input.expectedPid === undefined || candidate2.pid === input.expectedPid);
37853
+ const unverified = inspection.unverified.filter((candidate2) => input.expectedPid === undefined || candidate2.pid === input.expectedPid);
37854
+ if (verified.length > 1) {
37855
+ throw new Error(`Cannot choose between ${verified.length} orphaned EvoDev UI processes. Re-run with --port <port>.`);
37856
+ }
37857
+ const candidate = verified[0];
37858
+ if (candidate !== undefined) {
37859
+ if (!await verifyUiServerProcess(candidate, input.fetchImpl, input.processController)) {
37860
+ throw new Error("Cannot safely stop EvoDev UI because the orphaned runtime changed during verification.");
37861
+ }
37862
+ try {
37863
+ await input.processController.signal(candidate.pid, "SIGTERM");
37864
+ } catch (error) {
37865
+ if (await input.processController.isAlive(candidate.pid))
37866
+ throw error;
37867
+ }
37868
+ if (!await waitForUiProcessExit(candidate.pid, input.processController)) {
37869
+ throw new Error(`EvoDev UI process ${candidate.pid} did not stop within ${UI_RUNTIME_REQUEST_TIMEOUT_MS / 1000} seconds; no SIGKILL was sent.`);
37870
+ }
37871
+ return candidate;
37872
+ }
37873
+ if (unverified.length > 0) {
37874
+ throw new Error("Cannot safely stop EvoDev UI because a same-user EvoDev UI process did not pass the loopback identity check; no signal was sent.");
37875
+ }
37876
+ const host = input.selector.host ?? "127.0.0.1";
37877
+ const port = input.selector.port ?? DEFAULT_UI_PORT;
37878
+ if (await input.processController.isPortOccupied(host, port)) {
37879
+ throw new Error(`Cannot safely stop EvoDev UI: ${host}:${port} is occupied by an unmanaged process; no signal was sent.`);
37880
+ }
37881
+ return null;
37882
+ }
37883
+ async function inspectOrphanUiProcesses(input) {
37884
+ const snapshots = await input.processController.listProcesses();
37885
+ const candidates2 = snapshots.map((snapshot) => parseUiServerProcess({
37886
+ snapshot,
37887
+ currentUid: input.processController.currentUid,
37888
+ defaultPort: DEFAULT_UI_PORT
37889
+ })).filter((candidate) => candidate !== null).filter((candidate) => matchesUiStopSelector(candidate, input.selector));
37890
+ if (candidates2.length > MAX_ORPHAN_UI_CANDIDATES) {
37891
+ throw new Error("Too many EvoDev UI process candidates; no process was signaled.");
37892
+ }
37893
+ const verified = [];
37894
+ const unverified = [];
37895
+ const inspections = await Promise.all(candidates2.map(async (candidate) => ({
37896
+ candidate,
37897
+ verified: await verifyUiServerProcess(candidate, input.fetchImpl, input.processController)
37898
+ })));
37899
+ for (const inspection of inspections) {
37900
+ if (inspection.verified)
37901
+ verified.push(inspection.candidate);
37902
+ else
37903
+ unverified.push(inspection.candidate);
37904
+ }
37905
+ return { verified, unverified };
37906
+ }
37907
+ async function verifyUiServerProcess(candidate, fetchImpl, processController) {
37908
+ const health = await fetchUiIdentityResponse(new URL("/api/health", buildUiServerProcessUrl(candidate)), fetchImpl);
37909
+ if (health?.ok) {
37910
+ try {
37911
+ const body = await health.json();
37912
+ const state = parseUiRuntimeState(JSON.stringify(body.data));
37913
+ return state.component === "evodev-ui" && state.pid === candidate.pid && state.host === candidate.host && state.port === candidate.port;
37914
+ } catch {
37915
+ return false;
37916
+ }
37917
+ }
37918
+ if (health === null || health.status !== 404)
37919
+ return false;
37920
+ if (!await processController.ownsListeningPort(candidate.pid, candidate.port))
37921
+ return false;
37922
+ const root = await fetchUiIdentityResponse(new URL("/", buildUiServerProcessUrl(candidate)), fetchImpl);
37923
+ if (root === null || !root.ok || !(root.headers.get("content-type") ?? "").startsWith("text/html") || root.headers.get("cache-control") !== "no-store" || root.headers.get("x-content-type-options") !== "nosniff" || root.headers.get("x-frame-options") !== "DENY") {
37924
+ return false;
37925
+ }
37926
+ const html = await readResponseTextBounded(root, MAX_LEGACY_UI_HTML_BYTES);
37927
+ if (html === null)
37928
+ return false;
37929
+ return html.includes("<title>EvoDev UI</title>") && html.includes('<div id="root">') && html.includes("/assets/app.js");
37930
+ }
37931
+ async function fetchUiIdentityResponse(url, fetchImpl) {
37932
+ const controller = new AbortController;
37933
+ const timeout = setTimeout(() => controller.abort(), UI_RUNTIME_REQUEST_TIMEOUT_MS);
37934
+ try {
37935
+ return await fetchImpl(url, {
37936
+ headers: { accept: "application/json, text/html" },
37937
+ signal: controller.signal
37938
+ });
37939
+ } catch {
37940
+ return null;
37941
+ } finally {
37942
+ clearTimeout(timeout);
37943
+ }
37944
+ }
37945
+ async function readResponseTextBounded(response, maxBytes) {
37946
+ const contentLength = Number(response.headers.get("content-length"));
37947
+ if (Number.isFinite(contentLength) && contentLength > maxBytes)
37948
+ return null;
37949
+ if (response.body === null)
37950
+ return "";
37951
+ const reader = response.body.getReader();
37952
+ const decoder = new TextDecoder;
37953
+ let totalBytes = 0;
37954
+ let text2 = "";
37955
+ try {
37956
+ while (true) {
37957
+ const chunk = await reader.read();
37958
+ if (chunk.done)
37959
+ return text2 + decoder.decode();
37960
+ totalBytes += chunk.value.byteLength;
37961
+ if (totalBytes > maxBytes) {
37962
+ await reader.cancel().catch(() => {
37963
+ return;
37964
+ });
37965
+ return null;
37966
+ }
37967
+ text2 += decoder.decode(chunk.value, { stream: true });
37968
+ }
37969
+ } catch {
37970
+ return null;
37971
+ } finally {
37972
+ reader.releaseLock();
37973
+ }
37974
+ }
37975
+ async function waitForUiProcessExit(pid, processController) {
37976
+ const deadline = Date.now() + UI_RUNTIME_REQUEST_TIMEOUT_MS;
37977
+ while (Date.now() < deadline) {
37978
+ if (!await processController.isAlive(pid))
37979
+ return true;
37980
+ await delay(25);
37981
+ }
37982
+ return !await processController.isAlive(pid);
37983
+ }
37984
+ function buildUiServerProcessUrl(process5) {
37985
+ return `http://${process5.host}:${process5.port}`;
37986
+ }
37987
+ function matchesUiStopSelector(candidate, selector) {
37988
+ if (selector === undefined)
37989
+ return true;
37990
+ return (selector.host === null || selector.host === candidate.host) && (selector.port === null || selector.port === candidate.port);
37991
+ }
37992
+ function assertUiStopSelectorMatchesRuntime(selector, state) {
37993
+ if (selector.host !== null && selector.host !== state.host || selector.port !== null && selector.port !== state.port) {
37994
+ throw new Error(`Managed EvoDev UI is running at ${buildUiRuntimeBaseUrl(state)}; the stop selector does not match.`);
37995
+ }
37996
+ }
37268
37997
  function requireNoUiSubcommandArgs(command, argv) {
37269
37998
  if (argv.length > 0)
37270
37999
  throw new Error(`Unknown ui ${command} option: ${argv[0] ?? ""}`.trim());
37271
38000
  }
37272
38001
  async function delay(milliseconds) {
37273
- await new Promise((resolve13) => setTimeout(resolve13, milliseconds));
38002
+ await new Promise((resolve14) => setTimeout(resolve14, milliseconds));
37274
38003
  }
37275
38004
  async function writeProcessStdout(value) {
37276
- await new Promise((resolve13, reject) => {
38005
+ await new Promise((resolve14, reject) => {
37277
38006
  process.stdout.write(value, (error) => {
37278
38007
  if (error === null || error === undefined)
37279
- resolve13();
38008
+ resolve14();
37280
38009
  else
37281
38010
  reject(error);
37282
38011
  });
@@ -37301,6 +38030,9 @@ async function startUiServer(input) {
37301
38030
  const html = renderUiDashboardHtml();
37302
38031
  const webAssets = await loadUiWebAssets();
37303
38032
  const proposalExecution = input.proposalExecutionService ?? new RepoProposalExecutionService;
38033
+ const applyProposal = input.proposalApplicationRunner ?? ((request) => runRepoProposalApplication(request, {
38034
+ proposalExecution
38035
+ }));
37304
38036
  const evolutionJobs = input.evolutionJobService ?? new EvolutionJobService({ homeDir: input.homeDir });
37305
38037
  let runtimeState = null;
37306
38038
  let requestClose = null;
@@ -37417,7 +38149,7 @@ async function startUiServer(input) {
37417
38149
  knowledge: true,
37418
38150
  semanticKnowledge: true,
37419
38151
  recommendations: true,
37420
- improvementEvals: true,
38152
+ improvementEvals: false,
37421
38153
  forceRecommendations: true
37422
38154
  });
37423
38155
  writeJsonResponse(response, 202, { ok: true, data: result });
@@ -37525,6 +38257,24 @@ async function startUiServer(input) {
37525
38257
  writeJsonResponse(response, 202, { ok: true, data: result });
37526
38258
  return;
37527
38259
  }
38260
+ if (url2.pathname === "/api/proposals/application/start") {
38261
+ await requireAuthenticatedJsonPost({ request, response, token });
38262
+ if (response.writableEnded)
38263
+ return;
38264
+ const application = parseUiProposalApplicationRequest(await readRequestJson(request));
38265
+ const result = await applyProposal({
38266
+ homeDir: input.homeDir,
38267
+ proposalId: application.id,
38268
+ projectKey: application.projectKey,
38269
+ expectedReviewState: application.expectedReviewState,
38270
+ improvementEvalRequested: application.improvementEvalRequested,
38271
+ agent: application.agent,
38272
+ expectedFingerprint: application.expectedFingerprint,
38273
+ confirmDirty: application.confirmDirty
38274
+ });
38275
+ writeJsonResponse(response, 202, { ok: true, data: result });
38276
+ return;
38277
+ }
37528
38278
  writeJsonResponse(response, 404, { ok: false, error: "not found" });
37529
38279
  } catch (error) {
37530
38280
  if (error instanceof UiRequestError) {
@@ -37561,14 +38311,14 @@ async function startUiServer(input) {
37561
38311
  writeJsonResponse(response, 500, { ok: false, error: "dashboard request unavailable" });
37562
38312
  }
37563
38313
  });
37564
- await new Promise((resolve13, reject) => {
38314
+ await new Promise((resolve14, reject) => {
37565
38315
  const onError = (error) => {
37566
38316
  server.off("listening", onListening);
37567
- reject(error.code === "EADDRINUSE" ? new Error(`Cannot start EvoDev UI: ${input.flags.host}:${input.flags.port} is already in use and no running managed UI matched it.`) : error);
38317
+ reject(error.code === "EADDRINUSE" ? new Error(`Cannot start EvoDev UI: ${input.flags.host}:${input.flags.port} is already in use and no running managed UI matched it. Run \`evodev ui stop --host ${input.flags.host} --port ${input.flags.port}\` to recover a safely verified orphaned EvoDev UI process.`) : error);
37568
38318
  };
37569
38319
  const onListening = () => {
37570
38320
  server.off("error", onError);
37571
- resolve13();
38321
+ resolve14();
37572
38322
  };
37573
38323
  server.once("error", onError);
37574
38324
  server.once("listening", onListening);
@@ -37598,7 +38348,7 @@ async function startUiServer(input) {
37598
38348
  await Promise.all([proposalExecution.shutdown(), evolutionJobs.shutdown()]).catch(() => {
37599
38349
  return;
37600
38350
  });
37601
- await new Promise((resolve13) => server.close(() => resolve13()));
38351
+ await new Promise((resolve14) => server.close(() => resolve14()));
37602
38352
  throw error;
37603
38353
  }
37604
38354
  const url = `http://${input.flags.host}:${port}/#token=${token}`;
@@ -37606,7 +38356,7 @@ async function startUiServer(input) {
37606
38356
  input.write("Mode: local-only dashboard; only concrete repo proposals require review. Press Ctrl+C to stop.");
37607
38357
  if (input.flags.open)
37608
38358
  input.openBrowser(url, input.write);
37609
- return await new Promise((resolve13) => {
38359
+ return await new Promise((resolve14) => {
37610
38360
  let closing = false;
37611
38361
  const finish = () => {
37612
38362
  process.off("SIGINT", close);
@@ -37618,7 +38368,7 @@ async function startUiServer(input) {
37618
38368
  expectedToken: token
37619
38369
  }).catch((error) => {
37620
38370
  input.write(`Warning: dashboard stopped, but runtime state cleanup failed: ${describeError10(error)}`);
37621
- }).finally(() => resolve13(0));
38371
+ }).finally(() => resolve14(0));
37622
38372
  };
37623
38373
  const close = () => {
37624
38374
  if (closing)
@@ -37665,6 +38415,33 @@ function parseServeFlags(argv) {
37665
38415
  }
37666
38416
  return { host, port, open: open6 };
37667
38417
  }
38418
+ function parseUiStopSelector(argv) {
38419
+ let host = null;
38420
+ let port = null;
38421
+ for (let index = 0;index < argv.length; index += 1) {
38422
+ const option = argv[index];
38423
+ const value = argv[index + 1];
38424
+ if (option !== "--host" && option !== "--port") {
38425
+ throw new Error(`Unknown ui stop option: ${option ?? ""}`.trim());
38426
+ }
38427
+ if (value === undefined || value.startsWith("--")) {
38428
+ throw new Error(`Missing value for ${option}`);
38429
+ }
38430
+ if (option === "--host") {
38431
+ if (host !== null)
38432
+ throw new Error("Duplicate ui stop option: --host");
38433
+ host = validateUiBindHost(value);
38434
+ } else {
38435
+ if (port !== null)
38436
+ throw new Error("Duplicate ui stop option: --port");
38437
+ port = validateUiPort(Number(value));
38438
+ if (port === 0)
38439
+ throw new Error("UI stop port must be between 1 and 65535.");
38440
+ }
38441
+ index += 1;
38442
+ }
38443
+ return { host, port };
38444
+ }
37668
38445
  function validateUiBindHost(host) {
37669
38446
  if (host === "127.0.0.1" || host === "localhost")
37670
38447
  return host;
@@ -37866,6 +38643,51 @@ function parseUiProposalExecutionRequest(value) {
37866
38643
  confirmInconclusiveEval: record.confirmInconclusiveEval === true
37867
38644
  };
37868
38645
  }
38646
+ function parseUiProposalApplicationRequest(value) {
38647
+ const identity = parseUiProposalIdentitySubset(value);
38648
+ const record = value;
38649
+ const allowedKeys = new Set([
38650
+ "id",
38651
+ "projectKey",
38652
+ "expectedReviewState",
38653
+ "improvementEvalRequested",
38654
+ "agent",
38655
+ "expectedFingerprint",
38656
+ "confirmDirty"
38657
+ ]);
38658
+ if (Object.keys(record).some((key) => !allowedKeys.has(key))) {
38659
+ throw new UiRequestError(400, "repo proposal application request contains unsupported fields");
38660
+ }
38661
+ if (record.expectedReviewState !== "pending" && record.expectedReviewState !== "accepted" && record.expectedReviewState !== "rejected" && record.expectedReviewState !== "deferred") {
38662
+ throw new UiRequestError(400, "expected repo proposal review state is invalid");
38663
+ }
38664
+ if (record.improvementEvalRequested !== undefined && typeof record.improvementEvalRequested !== "boolean") {
38665
+ throw new UiRequestError(400, "improvement-evaluation selection is invalid");
38666
+ }
38667
+ if (record.expectedReviewState !== "accepted" && typeof record.improvementEvalRequested !== "boolean") {
38668
+ throw new UiRequestError(400, "improvement-evaluation selection is required when approving a proposal");
38669
+ }
38670
+ if (record.expectedReviewState === "accepted" && record.improvementEvalRequested !== undefined) {
38671
+ throw new UiRequestError(400, "an accepted proposal cannot change its improvement-evaluation selection");
38672
+ }
38673
+ if (record.agent !== "codex" && record.agent !== "claude") {
38674
+ throw new UiRequestError(400, "code agent selection is invalid");
38675
+ }
38676
+ if (typeof record.expectedFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(record.expectedFingerprint)) {
38677
+ throw new UiRequestError(400, "repository confirmation fingerprint is invalid");
38678
+ }
38679
+ if (record.confirmDirty !== undefined && typeof record.confirmDirty !== "boolean") {
38680
+ throw new UiRequestError(400, "dirty-worktree confirmation is invalid");
38681
+ }
38682
+ return {
38683
+ ...identity,
38684
+ expectedReviewState: record.expectedReviewState,
38685
+ improvementEvalRequested: typeof record.improvementEvalRequested === "boolean" ? record.improvementEvalRequested : undefined,
38686
+ agent: record.agent,
38687
+ expectedFingerprint: record.expectedFingerprint,
38688
+ confirmDirty: record.confirmDirty === true
38689
+ };
38690
+ }
37869
38691
  function parseUiProposalIdentitySubset(value) {
37870
38692
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
37871
38693
  throw new UiRequestError(400, "repo proposal execution request is invalid");
@@ -38004,7 +38826,7 @@ function getHelpText() {
38004
38826
  "",
38005
38827
  "Commands:",
38006
38828
  " init [claude|codex] Initialize EvoDev for selected Code Agents",
38007
- " init --yes Initialize both Claude and Codex with default answers",
38829
+ " init --yes Initialize both Agents and recommended Git projects",
38008
38830
  " doctor Check system, config, plugins, and built-in assets",
38009
38831
  " sync Sync enabled built-in assets to Code Agent user dirs",
38010
38832
  " config get Print ~/.evodev/settings.json without creating files",
@@ -38026,7 +38848,7 @@ function getHelpText() {
38026
38848
  " ui Start the local-only EvoDev web dashboard",
38027
38849
  " ui status Show the managed dashboard process status",
38028
38850
  " ui open Open the authenticated URL for the running dashboard",
38029
- " ui stop Gracefully stop the running dashboard",
38851
+ " ui stop Stop the managed or safely verified orphaned dashboard",
38030
38852
  " ui snapshot Print dashboard metadata without starting a server",
38031
38853
  " learn review Preview learning candidates without writing memory",
38032
38854
  " learn lint Validate learning candidate privacy/provenance fields",
@@ -38073,7 +38895,7 @@ function getHelpText() {
38073
38895
  ].join(`
38074
38896
  `);
38075
38897
  }
38076
- var CLI_VERSION = "0.0.1-alpha.12";
38898
+ var CLI_VERSION = "0.0.1-alpha.13";
38077
38899
  function getVersionText() {
38078
38900
  return `evodev ${CLI_VERSION}`;
38079
38901
  }
@@ -38723,6 +39545,7 @@ function parseInitFlags(argv) {
38723
39545
  answers.initializeConfig = true;
38724
39546
  answers.registerSkills = true;
38725
39547
  answers.registerAgents = true;
39548
+ answers.registerRecommendedProjects = true;
38726
39549
  answers.runDoctor = true;
38727
39550
  continue;
38728
39551
  }
@@ -38758,7 +39581,7 @@ if (isMainModule()) {
38758
39581
  process.exitCode = await run();
38759
39582
  }
38760
39583
  function isMainModule() {
38761
- return process.argv[1] !== undefined && realpathSync(fileURLToPath5(import.meta.url)) === realpathSync(resolve13(process.argv[1]));
39584
+ return process.argv[1] !== undefined && realpathSync(fileURLToPath5(import.meta.url)) === realpathSync(resolve14(process.argv[1]));
38762
39585
  }
38763
39586
  export {
38764
39587
  shouldRunPostCommandMaintenance,
@@ -38771,6 +39594,7 @@ export {
38771
39594
  runTeamCommand,
38772
39595
  runSyncCommand,
38773
39596
  runScheduleCommand,
39597
+ runRepoProposalApplication,
38774
39598
  runPluginCommand,
38775
39599
  runPendingImprovementEvaluations,
38776
39600
  runPackCommand,
@@ -38794,6 +39618,7 @@ export {
38794
39618
  resolveLazyEvolutionStatePath,
38795
39619
  resolveLaunchAgentPath,
38796
39620
  resolveLatestEvolutionJobPath,
39621
+ resolveInitProjectCandidates,
38797
39622
  resolveEvolutionJobPath,
38798
39623
  resolveDefaultCodeAgentSelections,
38799
39624
  renderUiDashboardHtml,
@@ -38825,6 +39650,7 @@ export {
38825
39650
  formatDoctorOutput,
38826
39651
  createSessionKnowledgeDistiller,
38827
39652
  createInteractivePrompter,
39653
+ createInitProjectChoices,
38828
39654
  createDefaultInitPluginRegistry,
38829
39655
  createCodexSessionProposalAnalyzer,
38830
39656
  createCodexSessionKnowledgeAnalyzer,