@pushpalsdev/cli 1.0.79 → 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
@@ -4059,7 +4076,7 @@ class PersistentSessionMemory {
4059
4076
  }
4060
4077
 
4061
4078
  // apps/remotebuddy/src/remotebuddy_main.ts
4062
- import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
4079
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4063
4080
  import { resolve as resolve5 } from "path";
4064
4081
 
4065
4082
  // apps/remotebuddy/src/autonomous_engine.ts
@@ -4516,6 +4533,144 @@ function uniqueLowercaseTokens(values, max = 24) {
4516
4533
  }
4517
4534
  return out;
4518
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
+ }
4519
4674
  var OBJECTIVE_TYPES = new Set([
4520
4675
  "flaky_test",
4521
4676
  "lint_fix",
@@ -4736,6 +4891,67 @@ function chooseRepoTargetProfile(profiles, hints, triggerType) {
4736
4891
  }
4737
4892
  return best?.profile ?? profiles[0] ?? null;
4738
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
+ }
4739
4955
  function adaptCandidateShapeToRepo(params) {
4740
4956
  const shape = params.shape;
4741
4957
  const scopeValidation = validateScopeInvariants(shape.component_area, shape.target_paths, shape.write_globs, {
@@ -5178,12 +5394,117 @@ function maxSignalScore(snapshot, types) {
5178
5394
  function maxTraitScore(snapshot, pattern) {
5179
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))));
5180
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
+ }
5181
5502
  function normalizeValidationIdeas(ideas) {
5182
5503
  const out = [];
5183
5504
  for (const idea of ideas) {
5184
- const canonical = canonicalizeValidationCommandForBun(idea);
5185
- if (canonical.startsWith("bun ")) {
5186
- out.push(canonical);
5505
+ const command = extractValidationCommandFromIdea(idea);
5506
+ if (isRepoNativeValidationCommand(command)) {
5507
+ out.push(command);
5187
5508
  continue;
5188
5509
  }
5189
5510
  const lower = idea.toLowerCase();
@@ -5198,6 +5519,16 @@ function normalizeValidationIdeas(ideas) {
5198
5519
  out.push("bun run test:root");
5199
5520
  return [...new Set(out)].slice(0, 5);
5200
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
+ }
5201
5532
  function inferComponentAreaFromText(text, repoTargets, triggerType) {
5202
5533
  const repoTargetMatch = chooseRepoTargetProfile(repoTargets ?? [], [text], triggerType);
5203
5534
  if (repoTargetMatch)
@@ -5554,6 +5885,7 @@ function buildCommitHistoryBlocks(params) {
5554
5885
  function buildEngineInspirationContext(params) {
5555
5886
  const oneSentence = asString2(params.vision.one_sentence);
5556
5887
  const keyItems = params.vision.key_items;
5888
+ const compiledRepoObjectives = compileRepoVisionObjectives({ vision: params.vision });
5557
5889
  const compiledObjectives = ENGINE_OBJECTIVE_BLUEPRINTS.map((blueprint) => {
5558
5890
  const lines = bucketLines(keyItems, blueprint.buckets);
5559
5891
  const evidence = keywordEvidence(lines, blueprint.keywordPattern);
@@ -5720,6 +6052,7 @@ function buildEngineInspirationContext(params) {
5720
6052
  }
5721
6053
  const buildingBlocks = [...buildingBlockMap.values()].sort((a, b) => b.score - a.score);
5722
6054
  return {
6055
+ compiled_repo_objectives: compiledRepoObjectives,
5723
6056
  compiled_objectives: compiledObjectives,
5724
6057
  opportunity_gaps: opportunityGaps,
5725
6058
  building_blocks: buildingBlocks,
@@ -5806,6 +6139,63 @@ function inferEngineTrialFromCandidate(candidate, engineInspiration) {
5806
6139
  hypothesis: fallback.hypothesis
5807
6140
  };
5808
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
+ }
5809
6199
  function buildEngineFallbackCandidates(params) {
5810
6200
  const maxCandidates = Number.isFinite(params.maxCandidates) ? Math.max(1, Math.min(6, Math.floor(params.maxCandidates))) : 3;
5811
6201
  const objectiveTitleById = new Map(params.engineInspiration.compiled_objectives.map((objective) => [
@@ -6130,6 +6520,7 @@ class RemoteBuddyAutonomousEngine {
6130
6520
  constraints: keyItems.constraints,
6131
6521
  non_goals: keyItems.nonGoals,
6132
6522
  metrics: keyItems.metrics,
6523
+ testing_criteria: keyItems.testingCriteria,
6133
6524
  risk_policy: keyItems.riskPolicy,
6134
6525
  operating_model: keyItems.operatingModel,
6135
6526
  governance: keyItems.governance
@@ -6860,8 +7251,16 @@ ${JSON.stringify(input.messages ?? [])}`),
6860
7251
  return;
6861
7252
  }
6862
7253
  let rawCandidates = Array.isArray(ideationJson.candidates) ? ideationJson.candidates : [];
7254
+ let rawCandidatesSource = "llm";
6863
7255
  if (rawCandidates.length === 0) {
6864
- 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({
6865
7264
  engineInspiration,
6866
7265
  snapshotTopSignals: snapshot.top_signals,
6867
7266
  visionSectionRefs: visionContext.section_numbers,
@@ -6870,8 +7269,9 @@ ${JSON.stringify(input.messages ?? [])}`),
6870
7269
  repoTargets
6871
7270
  });
6872
7271
  if (synthesized.length > 0) {
6873
- 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.`);
6874
7273
  rawCandidates = synthesized;
7274
+ rawCandidatesSource = repoSynthesized.length > 0 ? "repo_vision_fallback" : "engine_fallback";
6875
7275
  }
6876
7276
  }
6877
7277
  const normalizedCandidates = [];
@@ -6960,7 +7360,7 @@ ${JSON.stringify(input.messages ?? [])}`),
6960
7360
  console.warn(`[RemoteBuddyAutonomousEngine] dropping candidate ${candidate.id}: target_paths missing in repo ${missingTargetPaths.join(", ")}`);
6961
7361
  continue;
6962
7362
  }
6963
- if (!candidate.engine_trial) {
7363
+ if (!candidate.engine_trial && source !== "repo_vision_fallback") {
6964
7364
  const inferred = inferEngineTrialFromCandidate(candidate, engineInspiration);
6965
7365
  if (inferred) {
6966
7366
  candidate.engine_trial = {
@@ -6972,9 +7372,16 @@ ${JSON.stringify(input.messages ?? [])}`),
6972
7372
  normalizedCandidates.push(candidate);
6973
7373
  }
6974
7374
  };
6975
- ingestRawCandidates(rawCandidates, "llm");
7375
+ ingestRawCandidates(rawCandidates, rawCandidatesSource);
6976
7376
  if (normalizedCandidates.length === 0) {
6977
- 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({
6978
7385
  engineInspiration,
6979
7386
  snapshotTopSignals: snapshot.top_signals,
6980
7387
  visionSectionRefs: visionContext.section_numbers,
@@ -6983,7 +7390,7 @@ ${JSON.stringify(input.messages ?? [])}`),
6983
7390
  repoTargets
6984
7391
  });
6985
7392
  if (synthesizedFallback.length > 0) {
6986
- ingestRawCandidates(synthesizedFallback, "engine_fallback");
7393
+ ingestRawCandidates(synthesizedFallback, repoSynthesizedFallback.length > 0 ? "repo_vision_fallback" : "engine_fallback");
6987
7394
  }
6988
7395
  }
6989
7396
  candidatesPayload = normalizedCandidates.map((candidate) => ({
@@ -7806,7 +8213,7 @@ function ensureWriteGlobsCoverTargetPaths(targetPaths, writeGlobs) {
7806
8213
  }
7807
8214
  return { normalizedWriteGlobs, uncoveredTargets, addedGlobs };
7808
8215
  }
7809
- function buildExecutionGuidance(plan, targetPaths) {
8216
+ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = []) {
7810
8217
  const lines = [];
7811
8218
  const targets = normalizePathHints(targetPaths.length > 0 ? targetPaths : plan.scope.write_globs ?? []);
7812
8219
  if (targets.length > 0) {
@@ -7860,10 +8267,16 @@ function buildExecutionGuidance(plan, targetPaths) {
7860
8267
  for (const step of plan.validation_steps)
7861
8268
  lines.push(`- ${step}`);
7862
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
+ }
7863
8276
  return lines.join(`
7864
8277
  `).trim();
7865
8278
  }
7866
- 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;
7867
8280
  var VALIDATION_GENERIC_SAFE = /^(git\s+status\s+--porcelain|git\s+diff\b)/i;
7868
8281
  var PATH_TOKEN_REGEX = /\b([A-Za-z0-9._/\-\\]+\.[A-Za-z0-9._-]+)\b/g;
7869
8282
  function isCommandLikeValidationStep(step) {
@@ -7912,6 +8325,34 @@ function normalizeValidationSteps(steps, targetPaths) {
7912
8325
  }
7913
8326
  return out;
7914
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
+ }
7915
8356
  function defaultValidationStepsForRequest(prompt, targetPaths) {
7916
8357
  const text = prompt.toLowerCase();
7917
8358
  const concreteTargets = targetPaths.filter((entry) => entry && entry !== ".").slice(0, 4);
@@ -7945,6 +8386,44 @@ function sanitizePlannerWorkerInstruction(workerInstruction, canonicalInstructio
7945
8386
  }
7946
8387
  return value;
7947
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
+ }
7948
8427
  function explainJobFailureFromLogs(logs, fallbackMessage, fallbackDetail) {
7949
8428
  const lines = logs.map((row) => toSingleLine(row.message, 420)).filter(Boolean);
7950
8429
  const joined = lines.join(`
@@ -8281,6 +8760,22 @@ class RemoteBuddyOrchestrator {
8281
8760
  return [];
8282
8761
  }
8283
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
+ }
8284
8779
  markAutonomyFeedbackEventSeen(eventId) {
8285
8780
  const id = String(eventId ?? "").trim();
8286
8781
  if (!id)
@@ -8425,7 +8920,10 @@ class RemoteBuddyOrchestrator {
8425
8920
  return;
8426
8921
  }
8427
8922
  console.warn(`[RemoteBuddy] Fetching failure logs for job ${jobId}...`);
8428
- 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
+ ]);
8429
8927
  const clarificationFromLogs = extractClarificationFromJobFailure(message, detail, logs);
8430
8928
  if (clarificationFromLogs) {
8431
8929
  const tail2 = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
@@ -8435,9 +8933,10 @@ Recent logs:
8435
8933
  ${tail2.join(`
8436
8934
  `)}
8437
8935
  \`\`\`` : "";
8936
+ const toolText2 = formatToolRunDiagnostics(toolRuns);
8438
8937
  const clarificationMsg = `WorkerPal job ${shortJob} needs clarification before making changes: ${clarificationFromLogs}
8439
8938
 
8440
- ` + "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;
8441
8940
  await this.assistantMessage(sessionId, clarificationMsg, {
8442
8941
  correlationId: envelope.correlationId,
8443
8942
  turnId: envelope.turnId,
@@ -8445,7 +8944,7 @@ ${tail2.join(`
8445
8944
  });
8446
8945
  return;
8447
8946
  }
8448
- const explanation = explainJobFailureFromLogs(logs, message, detail);
8947
+ const explanation = explainJobFailureFromToolRuns(toolRuns) ?? explainJobFailureFromLogs(logs, message, detail);
8449
8948
  const tail = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
8450
8949
  const tailText = tail.length ? `
8451
8950
  Recent logs:
@@ -8453,7 +8952,8 @@ Recent logs:
8453
8952
  ${tail.join(`
8454
8953
  `)}
8455
8954
  \`\`\`` : "";
8456
- 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}`, {
8457
8957
  correlationId: envelope.correlationId,
8458
8958
  turnId: envelope.turnId,
8459
8959
  parentId: envelope.id
@@ -8728,6 +9228,17 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
8728
9228
  }
8729
9229
  return out;
8730
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
+ }
8731
9242
  getRecentContextSnapshot(sessionId = this.sessionId) {
8732
9243
  return this.sessionContext(sessionId).slice(-RemoteBuddyOrchestrator.MAX_CONTEXT);
8733
9244
  }
@@ -9190,7 +9701,12 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9190
9701
  const isAnalysisFromEngine = plan.intent === "analysis" && Boolean(autonomyMetadata);
9191
9702
  const requiresWorker = forceWorker && !isAnalysisFromEngine ? true : this.shouldForceDirectReply(prompt, plan.intent) ? false : plan.requires_worker;
9192
9703
  console.log("[RemoteBuddy] Planner output:", { plan, targetPath, requiresWorker });
9704
+ let requiredValidationSteps = [];
9193
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
+ }
9194
9710
  const scopeCoverage = ensureWriteGlobsCoverTargetPaths(targetPaths, plan.scope.write_globs);
9195
9711
  if (scopeCoverage.normalizedWriteGlobs.length > 0) {
9196
9712
  plan.scope.write_globs = scopeCoverage.normalizedWriteGlobs;
@@ -9240,7 +9756,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9240
9756
  }
9241
9757
  const canonicalInstruction = prompt.trim();
9242
9758
  const rawPlannerInstruction = sanitizePlannerWorkerInstruction(String(plan.worker_instruction ?? ""), canonicalInstruction);
9243
- const executionGuidance = buildExecutionGuidance(plan, targetPaths);
9759
+ const executionGuidance = buildExecutionGuidance(plan, targetPaths, requiredValidationSteps);
9244
9760
  const plannerWorkerInstruction = [rawPlannerInstruction, executionGuidance].filter(Boolean).join(`
9245
9761
 
9246
9762
  `).trim();
@@ -9346,6 +9862,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9346
9862
  } : {},
9347
9863
  acceptanceCriteria: plan.acceptance_criteria,
9348
9864
  validationSteps: plan.validation_steps,
9865
+ ...requiredValidationSteps.length > 0 ? { requiredValidationSteps } : {},
9349
9866
  queuePriority: priority,
9350
9867
  queueWaitBudgetMs,
9351
9868
  executionBudgetMs,
@@ -9675,6 +10192,7 @@ if (import.meta.main) {
9675
10192
  });
9676
10193
  }
9677
10194
  export {
10195
+ extractRequiredValidationStepsFromVisionMarkdown,
9678
10196
  buildTaskExecuteDedupeKey,
9679
10197
  RemoteBuddyOrchestrator
9680
10198
  };