@pushpalsdev/cli 1.0.78 → 1.0.80

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.
@@ -1088,7 +1088,7 @@ function loadPushPalsConfig(options = {}) {
1088
1088
  const canonical = coerceAutonomyComponentConfigKey(rawKey);
1089
1089
  if (!canonical)
1090
1090
  continue;
1091
- const parsed = typeof rawValue === "number" ? rawValue : typeof rawValue === "string" ? Number.parseInt(rawValue.trim(), 10) : Number.NaN;
1091
+ const parsed = rawValue;
1092
1092
  remoteAutonomyDispatchByComponent[canonical] = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : 0;
1093
1093
  }
1094
1094
  const workerNode = getObject(merged, "workerpals");
@@ -1603,6 +1603,9 @@ function classifyHeadingBucket(heading) {
1603
1603
  if (text.includes("non-goal") || text.includes("out of scope") || text.includes("not ")) {
1604
1604
  return "nonGoals";
1605
1605
  }
1606
+ if (text.includes("testing criteria") || text.includes("test criteria") || text.includes("required tests") || text.includes("required validation") || text.includes("validation criteria")) {
1607
+ return "testingCriteria";
1608
+ }
1606
1609
  if (text.includes("measure") || text.includes("metric") || text.includes("good looks like")) {
1607
1610
  return "metrics";
1608
1611
  }
@@ -1694,6 +1697,7 @@ function extractVisionKeyItems(markdown) {
1694
1697
  constraints: [],
1695
1698
  nonGoals: [],
1696
1699
  metrics: [],
1700
+ testingCriteria: [],
1697
1701
  riskPolicy: [],
1698
1702
  operatingModel: [],
1699
1703
  governance: []
@@ -1720,11 +1724,24 @@ function extractVisionKeyItems(markdown) {
1720
1724
  constraints: dedupeAndClamp(buckets.constraints),
1721
1725
  nonGoals: dedupeAndClamp(buckets.nonGoals),
1722
1726
  metrics: dedupeAndClamp(buckets.metrics),
1727
+ testingCriteria: dedupeAndClamp(buckets.testingCriteria),
1723
1728
  riskPolicy: dedupeAndClamp(buckets.riskPolicy),
1724
1729
  operatingModel: dedupeAndClamp(buckets.operatingModel),
1725
1730
  governance: dedupeAndClamp(buckets.governance)
1726
1731
  };
1727
1732
  }
1733
+ // packages/shared/src/tooling.ts
1734
+ var KNOWN_TOOL_NAMES = new Set([
1735
+ "bun",
1736
+ "codex",
1737
+ "docker",
1738
+ "gh",
1739
+ "git",
1740
+ "node",
1741
+ "npm",
1742
+ "python",
1743
+ "shell"
1744
+ ]);
1728
1745
  // packages/shared/src/session_event_visibility.ts
1729
1746
  var ALWAYS_VISIBLE_EVENT_TYPES = new Set(["question_asked"]);
1730
1747
  // packages/shared/src/localbuddy_runtime.ts
@@ -2053,6 +2070,10 @@ async function runProcessWithNode(command, opts) {
2053
2070
  });
2054
2071
  }
2055
2072
  var cachedCodexCommandPrefix = new Map;
2073
+ function bunCodexCommandFromEnv(env) {
2074
+ const bunBin = (env.PUSHPALS_BUN_BIN ?? "").trim();
2075
+ return bunBin ? [bunBin, "x", "--yes", "@openai/codex"] : [];
2076
+ }
2056
2077
  async function resolveCodexCommandPrefix(configuredCommand) {
2057
2078
  const override = codexCommandOverrideParts(configuredCommand);
2058
2079
  const cacheKey = override.join("\x00");
@@ -2071,6 +2092,7 @@ async function resolveCodexCommandPrefix(configuredCommand) {
2071
2092
  candidates.push(cmd);
2072
2093
  };
2073
2094
  pushCandidate(preferred);
2095
+ pushCandidate(bunCodexCommandFromEnv(process.env));
2074
2096
  const execPath = (process.execPath ?? "").trim();
2075
2097
  if (execPath) {
2076
2098
  const lower = execPath.toLowerCase();
@@ -4054,7 +4076,7 @@ class PersistentSessionMemory {
4054
4076
  }
4055
4077
 
4056
4078
  // apps/remotebuddy/src/remotebuddy_main.ts
4057
- import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
4079
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4058
4080
  import { resolve as resolve5 } from "path";
4059
4081
 
4060
4082
  // apps/remotebuddy/src/autonomous_engine.ts
@@ -4491,6 +4513,12 @@ function asNumber(value, fallback = 0) {
4491
4513
  const n = Number(value);
4492
4514
  return Number.isFinite(n) ? n : fallback;
4493
4515
  }
4516
+ function compactStatusDetail(value, max = 240) {
4517
+ const normalized = value.replace(/\s+/g, " ").trim();
4518
+ if (normalized.length <= max)
4519
+ return normalized;
4520
+ return `${normalized.slice(0, Math.max(0, max - 3))}...`;
4521
+ }
4494
4522
  function uniqueLowercaseTokens(values, max = 24) {
4495
4523
  const out = [];
4496
4524
  const seen = new Set;
@@ -4505,6 +4533,144 @@ function uniqueLowercaseTokens(values, max = 24) {
4505
4533
  }
4506
4534
  return out;
4507
4535
  }
4536
+ var CATEGORY_KEYWORD_RULES = [
4537
+ {
4538
+ category: "product_core",
4539
+ pattern: /\b(core|primary|match|game|gameplay|battle|battlefield|player|decision|territor|combat|strategy|mission|workflow|editor|dashboard|api)\b/i
4540
+ },
4541
+ {
4542
+ category: "user_experience",
4543
+ pattern: /\b(user experience|ux|ui|readab|legib|clarity|clear|shell|screen|navigation|control|input|touch|mobile|visual|presentation|feedback|discoverable|usable)\b/i
4544
+ },
4545
+ {
4546
+ category: "onboarding",
4547
+ pattern: /\b(onboard|new user|first[- ]?time|tutorial|learn|help|guide|activation|setup)\b/i
4548
+ },
4549
+ {
4550
+ category: "reliability",
4551
+ pattern: /\b(reliab|stable|stability|startup|trust|regression|failure|resilien|recover|fallback|safe|crash|broken|blocker)\b/i
4552
+ },
4553
+ {
4554
+ category: "validation",
4555
+ pattern: /\b(validation|validate|test|smoke|coverage|browser|e2e|end[- ]?to[- ]?end|ci|check|quality)\b/i
4556
+ },
4557
+ {
4558
+ category: "performance",
4559
+ pattern: /\b(performance|latency|smooth|jitter|lag|throughput|fps|render|memory|speed|fast|responsive)\b/i
4560
+ },
4561
+ {
4562
+ category: "maintainability",
4563
+ pattern: /\b(maintain|refactor|cleanup|architecture|structure|modular|debt|simplify|consistency|coherent)\b/i
4564
+ },
4565
+ {
4566
+ category: "delivery_loop",
4567
+ pattern: /\b(autonom|agent|worker|delivery loop|reliable autonomous delivery|merge|review|pr|pull request|dispatch|orchestrat|planner|compiler|ideation)\b/i
4568
+ },
4569
+ {
4570
+ category: "governance",
4571
+ pattern: /\b(policy|permission|scope|guardrail|risk|constraint|governance|approval|audit|security|non[- ]?goal)\b/i
4572
+ },
4573
+ {
4574
+ category: "growth",
4575
+ pattern: /\b(growth|retention|conversion|activation|adoption|audience|returning|replay|engagement)\b/i
4576
+ },
4577
+ {
4578
+ category: "content",
4579
+ pattern: /\b(content|variety|skin|cosmetic|faction|map|ship|projectile|enemy|level|mode|character)\b/i
4580
+ }
4581
+ ];
4582
+ var META_OBJECTIVE_CATEGORIES = new Set([
4583
+ "delivery_loop",
4584
+ "governance",
4585
+ "maintainability"
4586
+ ]);
4587
+ function slugifyObjectiveId(value, fallback) {
4588
+ const slug = asString2(value).toLowerCase().replace(/`([^`]+)`/g, "$1").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80);
4589
+ return slug || fallback;
4590
+ }
4591
+ function categorizeVisionText(text) {
4592
+ const matched = [];
4593
+ for (const rule of CATEGORY_KEYWORD_RULES) {
4594
+ if (rule.pattern.test(text))
4595
+ matched.push(rule.category);
4596
+ }
4597
+ if (matched.length === 0) {
4598
+ return { primary: "unknown", secondary: [] };
4599
+ }
4600
+ const [primary, ...secondary] = matched;
4601
+ return {
4602
+ primary,
4603
+ secondary: [...new Set(secondary)].slice(0, 4)
4604
+ };
4605
+ }
4606
+ function sourceBucketSectionRef(sourceBucket, sectionNumbers) {
4607
+ const preferredByBucket = {
4608
+ priorities: ["6", "5", "4"],
4609
+ objectives: ["7", "6", "5"],
4610
+ metrics: ["4", "6"],
4611
+ testing_criteria: ["12", "9", "4"],
4612
+ guardrails: ["9", "10", "3"],
4613
+ constraints: ["9", "5"],
4614
+ risk_policy: ["9", "10"],
4615
+ operating_model: ["11", "7"],
4616
+ governance: ["10", "9"],
4617
+ target_users: ["1"],
4618
+ non_goals: ["5", "9"],
4619
+ section: ["6", "7", "4", "3"]
4620
+ };
4621
+ const available = new Set(sectionNumbers);
4622
+ for (const ref of preferredByBucket[sourceBucket] ?? []) {
4623
+ if (available.has(ref))
4624
+ return ref;
4625
+ }
4626
+ return sectionNumbers[0] ?? "";
4627
+ }
4628
+ function categoryObjectiveType(category) {
4629
+ switch (category) {
4630
+ case "product_core":
4631
+ case "user_experience":
4632
+ case "onboarding":
4633
+ case "content":
4634
+ case "growth":
4635
+ return "feature_small";
4636
+ case "performance":
4637
+ case "reliability":
4638
+ case "maintainability":
4639
+ case "delivery_loop":
4640
+ case "governance":
4641
+ return "small_refactor";
4642
+ case "validation":
4643
+ return "flaky_test";
4644
+ default:
4645
+ return "small_refactor";
4646
+ }
4647
+ }
4648
+ function categoryTriggerType(category, topSignals) {
4649
+ const allowed = [
4650
+ "test_failure",
4651
+ "lint_failure",
4652
+ "typecheck_failure",
4653
+ "queue_health",
4654
+ "regret_signal"
4655
+ ];
4656
+ const strongestSignal = topSignals.map((signal) => ({
4657
+ type: asString2(signal.type),
4658
+ value: clamp012(asNumber(signal.value, 0))
4659
+ })).filter((signal) => allowed.includes(signal.type)).sort((a, b) => b.value - a.value)[0];
4660
+ if (category === "validation") {
4661
+ return strongestSignal?.type === "test_failure" ? "test_failure" : "queue_health";
4662
+ }
4663
+ if (category === "performance" || category === "reliability") {
4664
+ return strongestSignal?.type ?? "queue_health";
4665
+ }
4666
+ if (category === "delivery_loop" || category === "governance" || category === "maintainability") {
4667
+ return strongestSignal?.type === "regret_signal" ? "regret_signal" : "queue_health";
4668
+ }
4669
+ return "queue_health";
4670
+ }
4671
+ function isMetaRepoObjective(objective) {
4672
+ return META_OBJECTIVE_CATEGORIES.has(objective.category);
4673
+ }
4508
4674
  var OBJECTIVE_TYPES = new Set([
4509
4675
  "flaky_test",
4510
4676
  "lint_fix",
@@ -4725,6 +4891,67 @@ function chooseRepoTargetProfile(profiles, hints, triggerType) {
4725
4891
  }
4726
4892
  return best?.profile ?? profiles[0] ?? null;
4727
4893
  }
4894
+ function chooseRepoObjectiveTargetProfile(profiles, objective) {
4895
+ if (profiles.length === 0)
4896
+ return null;
4897
+ const hintTokens = [...new Set([...objective.keywords, ...tokenizePath(objective.title)])];
4898
+ const categories = new Set([
4899
+ objective.category,
4900
+ ...objective.secondary_categories
4901
+ ]);
4902
+ let best = null;
4903
+ for (const profile of profiles) {
4904
+ const label = profile.label.toLowerCase();
4905
+ const profileTokens = new Set(profile.keywords);
4906
+ let score = 0;
4907
+ for (const token of hintTokens) {
4908
+ if (profileTokens.has(token))
4909
+ score += 3;
4910
+ if (label.includes(token))
4911
+ score += 1;
4912
+ }
4913
+ const productSurface = /(^|\/)(app|src|components|component|screens|pages|routes|styles|assets)\b/i.test(label) || /\b(client|frontend|web|ui|ux|game|screen|view|layout)\b/i.test(label);
4914
+ const validationSurface = /(^|\/)(__tests__|tests?|e2e|smoke|specs?)\b/i.test(label) || /\b(test|smoke|spec)\b/i.test(label);
4915
+ const docsSurface = /\b(readme|vision|docs?)\b/i.test(label);
4916
+ const scriptSurface = /(^|\/)(scripts?|tools?)\b/i.test(label);
4917
+ const packageSurface = /\b(package\.json|tsconfig|eslint|prettier|config)\b/i.test(label);
4918
+ if (categories.has("product_core") || categories.has("user_experience") || categories.has("onboarding") || categories.has("content") || categories.has("growth")) {
4919
+ if (productSurface)
4920
+ score += 5;
4921
+ if (/\b(game|play|screen|route|layout|index|style|component)\b/i.test(label))
4922
+ score += 3;
4923
+ if (validationSurface)
4924
+ score -= 7;
4925
+ if (docsSurface || packageSurface || scriptSurface)
4926
+ score -= 4;
4927
+ }
4928
+ if (categories.has("validation")) {
4929
+ if (validationSurface || scriptSurface)
4930
+ score += 5;
4931
+ if (productSurface)
4932
+ score += 1;
4933
+ }
4934
+ if (categories.has("performance")) {
4935
+ if (productSurface || /\b(perf|render|animation|worker|server)\b/i.test(label))
4936
+ score += 4;
4937
+ if (docsSurface)
4938
+ score -= 3;
4939
+ }
4940
+ if (categories.has("reliability")) {
4941
+ if (productSurface || scriptSurface || packageSurface || /\b(config|startup|server)\b/i.test(label)) {
4942
+ score += 3;
4943
+ }
4944
+ }
4945
+ if (categories.has("delivery_loop") || categories.has("governance") || categories.has("maintainability")) {
4946
+ if (scriptSurface || packageSurface || /\b(src|utils?|lib|server|shared|policy)\b/i.test(label)) {
4947
+ score += 3;
4948
+ }
4949
+ }
4950
+ if (!best || score > best.score)
4951
+ best = { profile, score };
4952
+ }
4953
+ return best?.profile ?? chooseRepoTargetProfile(profiles, [objective.title], "queue_health");
4954
+ }
4728
4955
  function adaptCandidateShapeToRepo(params) {
4729
4956
  const shape = params.shape;
4730
4957
  const scopeValidation = validateScopeInvariants(shape.component_area, shape.target_paths, shape.write_globs, {
@@ -5167,12 +5394,117 @@ function maxSignalScore(snapshot, types) {
5167
5394
  function maxTraitScore(snapshot, pattern) {
5168
5395
  return clamp012(Math.max(0, ...snapshot.state_traits.filter((trait) => pattern.test(String(trait.focus ?? "")) || pattern.test(String(trait.evidence ?? "")) || pattern.test(String(trait.trait_id ?? ""))).map((trait) => asNumber(trait.score, 0))));
5169
5396
  }
5397
+ function repoObjectiveWeight(params) {
5398
+ const rank = params.priorityRank ?? 12;
5399
+ const sourceBase = params.sourceBucket === "priorities" ? 0.86 : params.sourceBucket === "objectives" ? 0.78 : params.sourceBucket === "metrics" ? 0.58 : params.sourceBucket === "section" ? 0.5 : 0.42;
5400
+ const rankPenalty = Math.min(0.28, Math.max(0, rank - 1) * 0.045);
5401
+ const metaPenalty = META_OBJECTIVE_CATEGORIES.has(params.category) ? 0.08 : 0;
5402
+ const explicitValidationBoost = params.category === "validation" || /\b(smoke|browser|validation|test)\b/i.test(params.text) ? 0.04 : 0;
5403
+ return clamp012(sourceBase - rankPenalty - metaPenalty + explicitValidationBoost);
5404
+ }
5405
+ function compileRepoVisionObjectives(params) {
5406
+ const sectionNumbers = params.vision.section_numbers ?? [];
5407
+ const keyItems = params.vision.key_items;
5408
+ const constraints = bucketLines(keyItems, [
5409
+ "guardrails",
5410
+ "constraints",
5411
+ "risk_policy",
5412
+ "non_goals"
5413
+ ]).slice(0, 12);
5414
+ const validationExpectations = [
5415
+ ...bucketLines(keyItems, ["testing_criteria"]),
5416
+ ...bucketLines(keyItems, ["metrics", "constraints", "risk_policy"]).filter((line) => /\b(validation|validate|test|smoke|browser|ci|check)\b/i.test(line))
5417
+ ].slice(0, 8);
5418
+ const successCriteria = bucketLines(keyItems, ["metrics", "objectives", "priorities"]).slice(0, 8);
5419
+ const entries = [];
5420
+ const seen = new Set;
5421
+ const addEntry = (rawTitle, sourceBucket, priorityRank, explicitSectionRef) => {
5422
+ const title = asString2(rawTitle);
5423
+ if (!title)
5424
+ return;
5425
+ const key = title.toLowerCase();
5426
+ if (seen.has(key))
5427
+ return;
5428
+ seen.add(key);
5429
+ const titleCategory = categorizeVisionText(title);
5430
+ const contextCategory = categorizeVisionText([constraints.join(" "), validationExpectations.join(" ")].join(`
5431
+ `));
5432
+ const secondaryCategories = [
5433
+ ...titleCategory.secondary,
5434
+ contextCategory.primary,
5435
+ ...contextCategory.secondary
5436
+ ].filter((category) => category !== "unknown" && category !== titleCategory.primary);
5437
+ const primaryCategory = titleCategory.primary === "unknown" && (sourceBucket === "priorities" || sourceBucket === "objectives") ? "product_core" : titleCategory.primary;
5438
+ const categorized = {
5439
+ primary: primaryCategory,
5440
+ secondary: [
5441
+ ...new Set(secondaryCategories.filter((category) => category !== primaryCategory))
5442
+ ].slice(0, 4)
5443
+ };
5444
+ const id = slugifyObjectiveId(title, `vision_objective_${entries.length + 1}`);
5445
+ const sectionRef = explicitSectionRef || sourceBucketSectionRef(sourceBucket, sectionNumbers) || "";
5446
+ const keywords = uniqueLowercaseTokens([
5447
+ ...tokenizePath(title),
5448
+ categorized.primary,
5449
+ ...categorized.secondary
5450
+ ]);
5451
+ const weight = repoObjectiveWeight({
5452
+ sourceBucket,
5453
+ priorityRank,
5454
+ category: categorized.primary,
5455
+ text: title
5456
+ });
5457
+ entries.push({
5458
+ id,
5459
+ title,
5460
+ category: categorized.primary,
5461
+ secondary_categories: categorized.secondary,
5462
+ priority_rank: priorityRank,
5463
+ source_bucket: sourceBucket,
5464
+ section_ref: sectionRef,
5465
+ weight,
5466
+ keywords,
5467
+ success_criteria: successCriteria,
5468
+ constraints,
5469
+ validation_expectations: validationExpectations,
5470
+ evidence: [
5471
+ `source_bucket=${sourceBucket}`,
5472
+ priorityRank != null ? `priority_rank=${priorityRank}` : "priority_rank=none",
5473
+ `category=${categorized.primary}`,
5474
+ `section_ref=${sectionRef || "none"}`
5475
+ ]
5476
+ });
5477
+ };
5478
+ keyItems.priorities.forEach((title, index) => addEntry(title, "priorities", index + 1));
5479
+ keyItems.objectives.forEach((title, index) => addEntry(title, "objectives", index + 1));
5480
+ keyItems.metrics.filter((title) => /\b(validation|smoke|browser|performance|reliab|startup)\b/i.test(title)).forEach((title, index) => addEntry(title, "metrics", index + 1));
5481
+ for (const section of params.vision.sections ?? []) {
5482
+ const sectionTitle = asString2(section.title);
5483
+ if (!sectionTitle)
5484
+ continue;
5485
+ if (/^(who this is for|the problem|scope|long-term|how decisions|get made)$/i.test(sectionTitle)) {
5486
+ continue;
5487
+ }
5488
+ const sectionNumber = asString2(section.number);
5489
+ const priorityRank = Number.isFinite(Number(sectionNumber)) ? Number(sectionNumber) : null;
5490
+ addEntry(sectionTitle, "section", priorityRank, sectionNumber);
5491
+ }
5492
+ return entries.sort((a, b) => {
5493
+ if (b.weight !== a.weight)
5494
+ return b.weight - a.weight;
5495
+ const aRank = a.priority_rank ?? Number.MAX_SAFE_INTEGER;
5496
+ const bRank = b.priority_rank ?? Number.MAX_SAFE_INTEGER;
5497
+ if (aRank !== bRank)
5498
+ return aRank - bRank;
5499
+ return a.id.localeCompare(b.id);
5500
+ });
5501
+ }
5170
5502
  function normalizeValidationIdeas(ideas) {
5171
5503
  const out = [];
5172
5504
  for (const idea of ideas) {
5173
- const canonical = canonicalizeValidationCommandForBun(idea);
5174
- if (canonical.startsWith("bun ")) {
5175
- out.push(canonical);
5505
+ const command = extractValidationCommandFromIdea(idea);
5506
+ if (isRepoNativeValidationCommand(command)) {
5507
+ out.push(command);
5176
5508
  continue;
5177
5509
  }
5178
5510
  const lower = idea.toLowerCase();
@@ -5187,6 +5519,16 @@ function normalizeValidationIdeas(ideas) {
5187
5519
  out.push("bun run test:root");
5188
5520
  return [...new Set(out)].slice(0, 5);
5189
5521
  }
5522
+ function extractValidationCommandFromIdea(value) {
5523
+ const raw = asString2(value);
5524
+ if (!raw)
5525
+ return "";
5526
+ const fenced = raw.match(/`([^`]+)`/)?.[1]?.trim();
5527
+ return (fenced || raw.replace(/^(run|execute|verify|validate|check)\s+/i, "")).trim();
5528
+ }
5529
+ function isRepoNativeValidationCommand(value) {
5530
+ return /^(bun|bunx|npm|npx|pnpm|yarn|node|python|python3|uv|pytest|vitest|jest|tsc|eslint|ruff|mypy|go|cargo|make|docker|pwsh|powershell|sh|bash)\b/i.test(value);
5531
+ }
5190
5532
  function inferComponentAreaFromText(text, repoTargets, triggerType) {
5191
5533
  const repoTargetMatch = chooseRepoTargetProfile(repoTargets ?? [], [text], triggerType);
5192
5534
  if (repoTargetMatch)
@@ -5543,6 +5885,7 @@ function buildCommitHistoryBlocks(params) {
5543
5885
  function buildEngineInspirationContext(params) {
5544
5886
  const oneSentence = asString2(params.vision.one_sentence);
5545
5887
  const keyItems = params.vision.key_items;
5888
+ const compiledRepoObjectives = compileRepoVisionObjectives({ vision: params.vision });
5546
5889
  const compiledObjectives = ENGINE_OBJECTIVE_BLUEPRINTS.map((blueprint) => {
5547
5890
  const lines = bucketLines(keyItems, blueprint.buckets);
5548
5891
  const evidence = keywordEvidence(lines, blueprint.keywordPattern);
@@ -5709,6 +6052,7 @@ function buildEngineInspirationContext(params) {
5709
6052
  }
5710
6053
  const buildingBlocks = [...buildingBlockMap.values()].sort((a, b) => b.score - a.score);
5711
6054
  return {
6055
+ compiled_repo_objectives: compiledRepoObjectives,
5712
6056
  compiled_objectives: compiledObjectives,
5713
6057
  opportunity_gaps: opportunityGaps,
5714
6058
  building_blocks: buildingBlocks,
@@ -5795,6 +6139,63 @@ function inferEngineTrialFromCandidate(candidate, engineInspiration) {
5795
6139
  hypothesis: fallback.hypothesis
5796
6140
  };
5797
6141
  }
6142
+ function buildRepoVisionFallbackCandidates(params) {
6143
+ const maxCandidates = Number.isFinite(params.maxCandidates) ? Math.max(1, Math.min(6, Math.floor(params.maxCandidates))) : 3;
6144
+ const sectionRefs = selectVisionSectionRefs(params.visionSectionRefs);
6145
+ const objectives = params.engineInspiration.compiled_repo_objectives.filter((objective) => objective.weight >= 0.42).sort((a, b) => {
6146
+ if (b.weight !== a.weight)
6147
+ return b.weight - a.weight;
6148
+ const aRank = a.priority_rank ?? Number.MAX_SAFE_INTEGER;
6149
+ const bRank = b.priority_rank ?? Number.MAX_SAFE_INTEGER;
6150
+ if (aRank !== bRank)
6151
+ return aRank - bRank;
6152
+ const aMeta = isMetaRepoObjective(a) ? 1 : 0;
6153
+ const bMeta = isMetaRepoObjective(b) ? 1 : 0;
6154
+ if (aMeta !== bMeta)
6155
+ return aMeta - bMeta;
6156
+ return a.id.localeCompare(b.id);
6157
+ }).slice(0, maxCandidates);
6158
+ return objectives.map((objective, idx) => {
6159
+ const target = chooseRepoObjectiveTargetProfile(params.repoTargets ?? [], objective);
6160
+ const targetPaths = target?.target_paths ?? [objective.section_ref ? `vision.md` : "README.md"];
6161
+ const writeGlobs = target?.write_globs ?? targetPaths;
6162
+ const componentArea = target?.component_area ?? (normalizeAutonomyComponentArea(pathDirname(targetPaths[0]) || targetPaths[0]) ?? "docs");
6163
+ const triggerType = categoryTriggerType(objective.category, params.snapshotTopSignals);
6164
+ const signalIds = pickSignalIdsForTrigger(params.snapshotTopSignals, triggerType);
6165
+ const sectionRef = objective.section_ref || sectionRefs[0] || "";
6166
+ const categorySummary = [
6167
+ objective.category,
6168
+ ...objective.secondary_categories.slice(0, 2)
6169
+ ].join(", ");
6170
+ return {
6171
+ id: `cand_repo_${objective.id}_${randomUUID().slice(0, 8)}`,
6172
+ title: `Vision objective: ${objective.title}`,
6173
+ objective_type: categoryObjectiveType(objective.category),
6174
+ problem_statement: `Advance the repo vision objective "${objective.title}" (${categorySummary}). ` + "Deliver one small, observable improvement using the repo's own product/domain language.",
6175
+ trigger_type: triggerType,
6176
+ component_area: componentArea,
6177
+ target_paths: targetPaths,
6178
+ scope: {
6179
+ read_anywhere: false,
6180
+ write_globs: writeGlobs
6181
+ },
6182
+ risk_level: "low",
6183
+ expected_validation: normalizeValidationIdeas(objective.validation_expectations.length > 0 ? objective.validation_expectations : ["bun run test:root"]),
6184
+ estimated_effort: idx === 0 ? "small" : "medium",
6185
+ why_now_signal_ids: signalIds,
6186
+ confidence: clamp012(0.5 + objective.weight * 0.45),
6187
+ vision_alignment_reason: `Highest repo vision category ${objective.category}; source=${objective.source_bucket}; ` + `priority=${objective.priority_rank ?? "n/a"}; section=${sectionRef || "n/a"}.`,
6188
+ vision_section_refs: sectionRef ? [sectionRef] : sectionRefs,
6189
+ feature_hypotheses: [
6190
+ objective.success_criteria[0] ? `Success signal: ${objective.success_criteria[0]}` : `Improve ${objective.title} without widening scope.`,
6191
+ objective.constraints[0] ? `Guardrail: ${objective.constraints[0]}` : "",
6192
+ objective.validation_expectations[0] ? `Validation expectation: ${objective.validation_expectations[0]}` : "Validate through the smallest repo-supported check."
6193
+ ].filter(Boolean),
6194
+ requires_user_input: false,
6195
+ question_if_blocked: ""
6196
+ };
6197
+ });
6198
+ }
5798
6199
  function buildEngineFallbackCandidates(params) {
5799
6200
  const maxCandidates = Number.isFinite(params.maxCandidates) ? Math.max(1, Math.min(6, Math.floor(params.maxCandidates))) : 3;
5800
6201
  const objectiveTitleById = new Map(params.engineInspiration.compiled_objectives.map((objective) => [
@@ -6050,6 +6451,9 @@ class RemoteBuddyAutonomousEngine {
6050
6451
  const maxPhaseTimeoutMs = Math.max(this.phaseTimeoutMs("ideation"), this.phaseTimeoutMs("scoring"), this.phaseTimeoutMs("planning"));
6051
6452
  return Math.max(this.cfg.tickIntervalMs * 3, this.cfg.ideationBudgetMs * 2 + maxPhaseTimeoutMs * 6, 30000);
6052
6453
  }
6454
+ lockStaleAfterMs() {
6455
+ return Math.max(this.phaseTimeoutMs("ideation") + 30000, this.cfg.heartbeatLogMs * 2, 120000);
6456
+ }
6053
6457
  cycleBudgetMs() {
6054
6458
  const ideationTimeoutMs = this.phaseTimeoutMs("ideation");
6055
6459
  const scoringTimeoutMs = this.phaseTimeoutMs("scoring");
@@ -6116,6 +6520,7 @@ class RemoteBuddyAutonomousEngine {
6116
6520
  constraints: keyItems.constraints,
6117
6521
  non_goals: keyItems.nonGoals,
6118
6522
  metrics: keyItems.metrics,
6523
+ testing_criteria: keyItems.testingCriteria,
6119
6524
  risk_policy: keyItems.riskPolicy,
6120
6525
  operating_model: keyItems.operatingModel,
6121
6526
  governance: keyItems.governance
@@ -6360,10 +6765,15 @@ class RemoteBuddyAutonomousEngine {
6360
6765
  body: JSON.stringify({
6361
6766
  sessionId: this.sessionId,
6362
6767
  runId,
6363
- ttlMs
6768
+ ttlMs,
6769
+ staleAfterMs: this.lockStaleAfterMs()
6364
6770
  })
6365
6771
  });
6366
- return res.ok;
6772
+ if (res.ok)
6773
+ return { ok: true };
6774
+ const payload = await res.json().catch(() => ({}));
6775
+ const reason = asString2(payload.reason ?? payload.message);
6776
+ return { ok: false, reason };
6367
6777
  }
6368
6778
  async renewDispatchLock(runId) {
6369
6779
  const res = await fetch(`${this.server}/autonomy/lock/renew`, {
@@ -6666,9 +7076,10 @@ ${JSON.stringify(input.messages ?? [])}`),
6666
7076
  let outcomeDetail = "not_dispatched";
6667
7077
  try {
6668
7078
  this.setPhase("acquire_lock");
6669
- lockAcquired = await this.acquireDispatchLock(runId);
7079
+ const lockResult = await this.acquireDispatchLock(runId);
7080
+ lockAcquired = lockResult.ok;
6670
7081
  if (!lockAcquired) {
6671
- outcomeDetail = "lock_not_acquired";
7082
+ outcomeDetail = lockResult.reason ? compactStatusDetail(`lock_not_acquired:${lockResult.reason}`) : "lock_not_acquired";
6672
7083
  return;
6673
7084
  }
6674
7085
  this.setPhase("prepare_worktree");
@@ -6840,8 +7251,16 @@ ${JSON.stringify(input.messages ?? [])}`),
6840
7251
  return;
6841
7252
  }
6842
7253
  let rawCandidates = Array.isArray(ideationJson.candidates) ? ideationJson.candidates : [];
7254
+ let rawCandidatesSource = "llm";
6843
7255
  if (rawCandidates.length === 0) {
6844
- const synthesized = buildEngineFallbackCandidates({
7256
+ const repoSynthesized = buildRepoVisionFallbackCandidates({
7257
+ engineInspiration,
7258
+ snapshotTopSignals: snapshot.top_signals,
7259
+ visionSectionRefs: visionContext.section_numbers,
7260
+ maxCandidates: Math.max(1, Math.min(3, this.cfg.topK)),
7261
+ repoTargets
7262
+ });
7263
+ const synthesized = repoSynthesized.length > 0 ? repoSynthesized : buildEngineFallbackCandidates({
6845
7264
  engineInspiration,
6846
7265
  snapshotTopSignals: snapshot.top_signals,
6847
7266
  visionSectionRefs: visionContext.section_numbers,
@@ -6850,8 +7269,9 @@ ${JSON.stringify(input.messages ?? [])}`),
6850
7269
  repoTargets
6851
7270
  });
6852
7271
  if (synthesized.length > 0) {
6853
- console.log(`[RemoteBuddyAutonomousEngine] tick ${runId}: ideation returned no candidates; using ${synthesized.length} deterministic engine-inspiration fallback candidates.`);
7272
+ console.log(`[RemoteBuddyAutonomousEngine] tick ${runId}: ideation returned no candidates; using ${synthesized.length} deterministic ${repoSynthesized.length > 0 ? "repo-vision" : "engine-inspiration"} fallback candidates.`);
6854
7273
  rawCandidates = synthesized;
7274
+ rawCandidatesSource = repoSynthesized.length > 0 ? "repo_vision_fallback" : "engine_fallback";
6855
7275
  }
6856
7276
  }
6857
7277
  const normalizedCandidates = [];
@@ -6940,7 +7360,7 @@ ${JSON.stringify(input.messages ?? [])}`),
6940
7360
  console.warn(`[RemoteBuddyAutonomousEngine] dropping candidate ${candidate.id}: target_paths missing in repo ${missingTargetPaths.join(", ")}`);
6941
7361
  continue;
6942
7362
  }
6943
- if (!candidate.engine_trial) {
7363
+ if (!candidate.engine_trial && source !== "repo_vision_fallback") {
6944
7364
  const inferred = inferEngineTrialFromCandidate(candidate, engineInspiration);
6945
7365
  if (inferred) {
6946
7366
  candidate.engine_trial = {
@@ -6952,9 +7372,16 @@ ${JSON.stringify(input.messages ?? [])}`),
6952
7372
  normalizedCandidates.push(candidate);
6953
7373
  }
6954
7374
  };
6955
- ingestRawCandidates(rawCandidates, "llm");
7375
+ ingestRawCandidates(rawCandidates, rawCandidatesSource);
6956
7376
  if (normalizedCandidates.length === 0) {
6957
- const synthesizedFallback = buildEngineFallbackCandidates({
7377
+ const repoSynthesizedFallback = buildRepoVisionFallbackCandidates({
7378
+ engineInspiration,
7379
+ snapshotTopSignals: snapshot.top_signals,
7380
+ visionSectionRefs: visionContext.section_numbers,
7381
+ maxCandidates: Math.max(1, Math.min(3, this.cfg.topK)),
7382
+ repoTargets
7383
+ });
7384
+ const synthesizedFallback = repoSynthesizedFallback.length > 0 ? repoSynthesizedFallback : buildEngineFallbackCandidates({
6958
7385
  engineInspiration,
6959
7386
  snapshotTopSignals: snapshot.top_signals,
6960
7387
  visionSectionRefs: visionContext.section_numbers,
@@ -6963,7 +7390,7 @@ ${JSON.stringify(input.messages ?? [])}`),
6963
7390
  repoTargets
6964
7391
  });
6965
7392
  if (synthesizedFallback.length > 0) {
6966
- ingestRawCandidates(synthesizedFallback, "engine_fallback");
7393
+ ingestRawCandidates(synthesizedFallback, repoSynthesizedFallback.length > 0 ? "repo_vision_fallback" : "engine_fallback");
6967
7394
  }
6968
7395
  }
6969
7396
  candidatesPayload = normalizedCandidates.map((candidate) => ({
@@ -7786,7 +8213,7 @@ function ensureWriteGlobsCoverTargetPaths(targetPaths, writeGlobs) {
7786
8213
  }
7787
8214
  return { normalizedWriteGlobs, uncoveredTargets, addedGlobs };
7788
8215
  }
7789
- function buildExecutionGuidance(plan, targetPaths) {
8216
+ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = []) {
7790
8217
  const lines = [];
7791
8218
  const targets = normalizePathHints(targetPaths.length > 0 ? targetPaths : plan.scope.write_globs ?? []);
7792
8219
  if (targets.length > 0) {
@@ -7840,10 +8267,16 @@ function buildExecutionGuidance(plan, targetPaths) {
7840
8267
  for (const step of plan.validation_steps)
7841
8268
  lines.push(`- ${step}`);
7842
8269
  }
8270
+ if (requiredValidationSteps.length > 0) {
8271
+ lines.push("Required vision.md testing criteria:");
8272
+ for (const step of requiredValidationSteps)
8273
+ lines.push(`- ${step}`);
8274
+ lines.push("- These repo-level checks are mandatory before reporting completion or publishing a PR.");
8275
+ }
7843
8276
  return lines.join(`
7844
8277
  `).trim();
7845
8278
  }
7846
- var VALIDATION_COMMAND_PREFIX = /^(git|bun|bunx|node|python|python3|uv|pytest|vitest|jest|tsc|eslint|ruff|mypy|go|cargo|make|docker|pwsh|powershell|sh|bash)\b/i;
8279
+ var VALIDATION_COMMAND_PREFIX = /^(git|bun|bunx|npm|npx|pnpm|yarn|node|python|python3|uv|pytest|vitest|jest|tsc|eslint|ruff|mypy|go|cargo|make|docker|pwsh|powershell|sh|bash)\b/i;
7847
8280
  var VALIDATION_GENERIC_SAFE = /^(git\s+status\s+--porcelain|git\s+diff\b)/i;
7848
8281
  var PATH_TOKEN_REGEX = /\b([A-Za-z0-9._/\-\\]+\.[A-Za-z0-9._-]+)\b/g;
7849
8282
  function isCommandLikeValidationStep(step) {
@@ -7892,6 +8325,34 @@ function normalizeValidationSteps(steps, targetPaths) {
7892
8325
  }
7893
8326
  return out;
7894
8327
  }
8328
+ function extractCommandFromValidationCriterion(value) {
8329
+ const raw = String(value ?? "").trim();
8330
+ if (!raw)
8331
+ return "";
8332
+ const fenced = raw.match(/`([^`]+)`/)?.[1]?.trim();
8333
+ const candidate = fenced || raw.replace(/^(run|execute|verify|validate|check)\s+/i, "").trim();
8334
+ return candidate.trim();
8335
+ }
8336
+ function extractRequiredValidationStepsFromVisionMarkdown(markdown) {
8337
+ const criteria = extractVisionKeyItems(markdown).testingCriteria;
8338
+ const out = [];
8339
+ const seen = new Set;
8340
+ for (const criterion of criteria) {
8341
+ const command = extractCommandFromValidationCriterion(criterion);
8342
+ if (!command)
8343
+ continue;
8344
+ if (!isCommandLikeValidationStep(command))
8345
+ continue;
8346
+ const key = command.toLowerCase();
8347
+ if (seen.has(key))
8348
+ continue;
8349
+ seen.add(key);
8350
+ out.push(command);
8351
+ if (out.length >= 12)
8352
+ break;
8353
+ }
8354
+ return out;
8355
+ }
7895
8356
  function defaultValidationStepsForRequest(prompt, targetPaths) {
7896
8357
  const text = prompt.toLowerCase();
7897
8358
  const concreteTargets = targetPaths.filter((entry) => entry && entry !== ".").slice(0, 4);
@@ -7925,6 +8386,44 @@ function sanitizePlannerWorkerInstruction(workerInstruction, canonicalInstructio
7925
8386
  }
7926
8387
  return value;
7927
8388
  }
8389
+ function summarizeToolRun(toolRun) {
8390
+ const command = toSingleLine(toolRun.commandLine, 160) || (Array.isArray(toolRun.argv) ? toSingleLine(toolRun.argv.join(" "), 160) : "");
8391
+ const parts = [
8392
+ `tool=${toSingleLine(toolRun.tool, 40) || "unknown"}`,
8393
+ toolRun.phase ? `phase=${toSingleLine(toolRun.phase, 60)}` : "",
8394
+ command ? `cmd=${command}` : "",
8395
+ toolRun.failureClass ? `class=${toSingleLine(toolRun.failureClass, 60)}` : "",
8396
+ typeof toolRun.retryable === "boolean" ? `retryable=${toolRun.retryable ? "yes" : "no"}` : "",
8397
+ typeof toolRun.exitCode === "number" ? `exit=${toolRun.exitCode}` : "",
8398
+ toolRun.remediation ? `fix=${toSingleLine(toolRun.remediation, 220)}` : ""
8399
+ ].filter(Boolean);
8400
+ return parts.join(" | ");
8401
+ }
8402
+ function explainJobFailureFromToolRuns(toolRuns) {
8403
+ const failed = toolRuns.find((entry) => entry && entry.ok === false);
8404
+ if (!failed)
8405
+ return null;
8406
+ const tool = toSingleLine(failed.tool, 40) || "unknown tool";
8407
+ const failureClass = toSingleLine(failed.failureClass, 80) || "tool failure";
8408
+ const remediation = toSingleLine(failed.remediation, 260);
8409
+ const command = toSingleLine(failed.commandLine, 140) || (Array.isArray(failed.argv) ? toSingleLine(failed.argv.join(" "), 140) : "");
8410
+ return [
8411
+ `Latest tool failure: ${tool} reported ${failureClass}`,
8412
+ command ? `while running ${command}` : "",
8413
+ remediation ? `Recommended fix: ${remediation}` : ""
8414
+ ].filter(Boolean).join(". ");
8415
+ }
8416
+ function formatToolRunDiagnostics(toolRuns) {
8417
+ const failed = toolRuns.filter((entry) => entry && entry.ok === false).slice(0, 4);
8418
+ if (failed.length === 0)
8419
+ return "";
8420
+ return `
8421
+ Tool diagnostics:
8422
+ \`\`\`
8423
+ ${failed.map(summarizeToolRun).join(`
8424
+ `)}
8425
+ \`\`\``;
8426
+ }
7928
8427
  function explainJobFailureFromLogs(logs, fallbackMessage, fallbackDetail) {
7929
8428
  const lines = logs.map((row) => toSingleLine(row.message, 420)).filter(Boolean);
7930
8429
  const joined = lines.join(`
@@ -8261,6 +8760,22 @@ class RemoteBuddyOrchestrator {
8261
8760
  return [];
8262
8761
  }
8263
8762
  }
8763
+ async fetchJobToolRuns(jobId, limit = 20) {
8764
+ try {
8765
+ const res = await fetch(`${this.server}/jobs/${jobId}/tool-runs?limit=${Math.max(1, Math.min(100, limit))}`, {
8766
+ method: "GET",
8767
+ headers: this.authHeaders()
8768
+ });
8769
+ if (!res.ok)
8770
+ return [];
8771
+ const data = await res.json();
8772
+ if (!data.ok || !Array.isArray(data.toolRuns))
8773
+ return [];
8774
+ return data.toolRuns.filter((row) => row && typeof row.tool === "string").slice(0, 20);
8775
+ } catch {
8776
+ return [];
8777
+ }
8778
+ }
8264
8779
  markAutonomyFeedbackEventSeen(eventId) {
8265
8780
  const id = String(eventId ?? "").trim();
8266
8781
  if (!id)
@@ -8405,7 +8920,10 @@ class RemoteBuddyOrchestrator {
8405
8920
  return;
8406
8921
  }
8407
8922
  console.warn(`[RemoteBuddy] Fetching failure logs for job ${jobId}...`);
8408
- const logs = await this.fetchJobLogs(jobId, 80);
8923
+ const [logs, toolRuns] = await Promise.all([
8924
+ this.fetchJobLogs(jobId, 80),
8925
+ this.fetchJobToolRuns(jobId, 20)
8926
+ ]);
8409
8927
  const clarificationFromLogs = extractClarificationFromJobFailure(message, detail, logs);
8410
8928
  if (clarificationFromLogs) {
8411
8929
  const tail2 = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
@@ -8415,9 +8933,10 @@ Recent logs:
8415
8933
  ${tail2.join(`
8416
8934
  `)}
8417
8935
  \`\`\`` : "";
8936
+ const toolText2 = formatToolRunDiagnostics(toolRuns);
8418
8937
  const clarificationMsg = `WorkerPal job ${shortJob} needs clarification before making changes: ${clarificationFromLogs}
8419
8938
 
8420
- ` + "Reply with the missing details and I will enqueue a focused follow-up request." + tailText2;
8939
+ ` + "Reply with the missing details and I will enqueue a focused follow-up request." + toolText2 + tailText2;
8421
8940
  await this.assistantMessage(sessionId, clarificationMsg, {
8422
8941
  correlationId: envelope.correlationId,
8423
8942
  turnId: envelope.turnId,
@@ -8425,7 +8944,7 @@ ${tail2.join(`
8425
8944
  });
8426
8945
  return;
8427
8946
  }
8428
- const explanation = explainJobFailureFromLogs(logs, message, detail);
8947
+ const explanation = explainJobFailureFromToolRuns(toolRuns) ?? explainJobFailureFromLogs(logs, message, detail);
8429
8948
  const tail = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
8430
8949
  const tailText = tail.length ? `
8431
8950
  Recent logs:
@@ -8433,7 +8952,8 @@ Recent logs:
8433
8952
  ${tail.join(`
8434
8953
  `)}
8435
8954
  \`\`\`` : "";
8436
- await this.assistantMessage(sessionId, `Diagnosis for job ${shortJob}: ${explanation}${tailText}`, {
8955
+ const toolText = formatToolRunDiagnostics(toolRuns);
8956
+ await this.assistantMessage(sessionId, `Diagnosis for job ${shortJob}: ${explanation}${toolText}${tailText}`, {
8437
8957
  correlationId: envelope.correlationId,
8438
8958
  turnId: envelope.turnId,
8439
8959
  parentId: envelope.id
@@ -8708,6 +9228,17 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
8708
9228
  }
8709
9229
  return out;
8710
9230
  }
9231
+ loadVisionRequiredValidationSteps() {
9232
+ const visionPath = resolve5(this.repo, "vision.md");
9233
+ if (!existsSync5(visionPath))
9234
+ return [];
9235
+ try {
9236
+ return extractRequiredValidationStepsFromVisionMarkdown(readFileSync5(visionPath, "utf8"));
9237
+ } catch (err) {
9238
+ console.warn("[RemoteBuddy] Could not read vision.md testing criteria:", err);
9239
+ return [];
9240
+ }
9241
+ }
8711
9242
  getRecentContextSnapshot(sessionId = this.sessionId) {
8712
9243
  return this.sessionContext(sessionId).slice(-RemoteBuddyOrchestrator.MAX_CONTEXT);
8713
9244
  }
@@ -9170,7 +9701,12 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9170
9701
  const isAnalysisFromEngine = plan.intent === "analysis" && Boolean(autonomyMetadata);
9171
9702
  const requiresWorker = forceWorker && !isAnalysisFromEngine ? true : this.shouldForceDirectReply(prompt, plan.intent) ? false : plan.requires_worker;
9172
9703
  console.log("[RemoteBuddy] Planner output:", { plan, targetPath, requiresWorker });
9704
+ let requiredValidationSteps = [];
9173
9705
  if (requiresWorker) {
9706
+ requiredValidationSteps = this.loadVisionRequiredValidationSteps();
9707
+ if (requiredValidationSteps.length > 0) {
9708
+ console.log(`[RemoteBuddy] Loaded ${requiredValidationSteps.length} required validation step(s) from vision.md testing criteria.`);
9709
+ }
9174
9710
  const scopeCoverage = ensureWriteGlobsCoverTargetPaths(targetPaths, plan.scope.write_globs);
9175
9711
  if (scopeCoverage.normalizedWriteGlobs.length > 0) {
9176
9712
  plan.scope.write_globs = scopeCoverage.normalizedWriteGlobs;
@@ -9220,7 +9756,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9220
9756
  }
9221
9757
  const canonicalInstruction = prompt.trim();
9222
9758
  const rawPlannerInstruction = sanitizePlannerWorkerInstruction(String(plan.worker_instruction ?? ""), canonicalInstruction);
9223
- const executionGuidance = buildExecutionGuidance(plan, targetPaths);
9759
+ const executionGuidance = buildExecutionGuidance(plan, targetPaths, requiredValidationSteps);
9224
9760
  const plannerWorkerInstruction = [rawPlannerInstruction, executionGuidance].filter(Boolean).join(`
9225
9761
 
9226
9762
  `).trim();
@@ -9326,6 +9862,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9326
9862
  } : {},
9327
9863
  acceptanceCriteria: plan.acceptance_criteria,
9328
9864
  validationSteps: plan.validation_steps,
9865
+ ...requiredValidationSteps.length > 0 ? { requiredValidationSteps } : {},
9329
9866
  queuePriority: priority,
9330
9867
  queueWaitBudgetMs,
9331
9868
  executionBudgetMs,
@@ -9655,6 +10192,7 @@ if (import.meta.main) {
9655
10192
  });
9656
10193
  }
9657
10194
  export {
10195
+ extractRequiredValidationStepsFromVisionMarkdown,
9658
10196
  buildTaskExecuteDedupeKey,
9659
10197
  RemoteBuddyOrchestrator
9660
10198
  };