opencode-agent-skill 13.0.0-beta.1 → 14.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +1557 -606
  2. package/bin/ocskill.mjs +172 -24
  3. package/docs/OPENCODE-COMPAT.md +34 -95
  4. package/docs/PI-COMPAT.md +188 -0
  5. package/docs/V14-CONTEXT-MEMORY-FABRIC.md +70 -0
  6. package/docs/V14.1-QUALITY-PERFORMANCE-FABRIC.md +114 -0
  7. package/docs/V14.2-TURBO-WEAK-MODEL-RUNTIME.md +448 -0
  8. package/evals/v14/tasks.json +46 -0
  9. package/global-config/agents/executor.md +7 -0
  10. package/global-config/agents/visual-verifier.md +22 -3
  11. package/global-config/commands/run.md +27 -20
  12. package/global-config/plugins/ues-router/command-runtime.js +54 -0
  13. package/global-config/plugins/ues-router/index.js +30 -24
  14. package/global-config/plugins/ues-router/policy-runtime.js +7 -0
  15. package/global-config/plugins/ues-router/router.js +12 -2
  16. package/global-config/skills/ecommerce-engineering/SKILL.md +1 -1
  17. package/global-config/skills/file-upload-engineering/SKILL.md +1 -1
  18. package/global-config/skills/git-safety/SKILL.md +1 -1
  19. package/global-config/skills/nestjs-engineering/SKILL.md +1 -1
  20. package/global-config/skills/performance-engineering/SKILL.md +1 -1
  21. package/global-config/skills/react-native-engineering/SKILL.md +1 -1
  22. package/global-config/skills/rest-api-design/SKILL.md +1 -1
  23. package/global-config/skills/ui-ux-engineering/SKILL.md +1 -1
  24. package/lib/adaptive-context-budget.mjs +97 -0
  25. package/lib/affected-tests.mjs +260 -0
  26. package/lib/benchmark-confidence.mjs +41 -2
  27. package/lib/browser-mcp-routing.mjs +166 -0
  28. package/lib/capability-fabric.mjs +336 -0
  29. package/lib/capability-registry.mjs +9 -0
  30. package/lib/context-engine-v11.mjs +65 -1
  31. package/lib/context-graph-rank.mjs +118 -0
  32. package/lib/context-manifest.mjs +97 -18
  33. package/lib/control-center.mjs +19 -1
  34. package/lib/dynamic-workflow.mjs +3 -1
  35. package/lib/evidence-store.mjs +82 -1
  36. package/lib/hierarchical-context.mjs +215 -0
  37. package/lib/memory-engine.mjs +465 -0
  38. package/lib/model-performance.mjs +33 -8
  39. package/lib/model-policy.mjs +3 -3
  40. package/lib/orchestrator-policy.mjs +5 -209
  41. package/lib/performance-fabric.mjs +229 -0
  42. package/lib/pi-rpc-pool.mjs +433 -0
  43. package/lib/process-hang-detector.mjs +83 -0
  44. package/lib/process-supervisor.mjs +193 -0
  45. package/lib/prompt-cache.mjs +2 -0
  46. package/lib/repo-graph.mjs +53 -2
  47. package/lib/runtime-config.mjs +31 -0
  48. package/lib/safety.mjs +132 -0
  49. package/lib/semantic-index.mjs +52 -3
  50. package/lib/skill-compiler.mjs +128 -0
  51. package/lib/skill-quality.mjs +48 -2
  52. package/lib/task-engine.mjs +66 -5
  53. package/lib/task-policy.mjs +235 -0
  54. package/lib/verification-broker.mjs +284 -0
  55. package/lib/verification-command.mjs +111 -0
  56. package/lib/windows-shim.mjs +35 -0
  57. package/lib/workspace-fingerprint.mjs +198 -0
  58. package/package.json +52 -42
  59. package/pi/extensions/ues-child-runtime.ts +238 -0
  60. package/pi/extensions/ues.ts +3200 -0
  61. package/pi/prompts/ues-audit.md +9 -0
  62. package/pi/prompts/ues-critique.md +9 -0
  63. package/pi/prompts/ues-debug.md +9 -0
  64. package/pi/prompts/ues-feature.md +9 -0
  65. package/pi/prompts/ues-fix.md +9 -0
  66. package/pi/prompts/ues-plan.md +9 -0
  67. package/pi/prompts/ues-research.md +9 -0
  68. package/pi/prompts/ues-resume.md +9 -0
  69. package/pi/prompts/ues-review.md +7 -0
  70. package/pi/prompts/ues-run.md +17 -0
  71. package/pi/prompts/ues-verify.md +9 -0
  72. package/scripts/check-release-consistency.mjs +119 -185
  73. package/scripts/check-runtime-exports.mjs +66 -0
  74. package/scripts/check-source-integrity.mjs +184 -0
  75. package/scripts/eval-pi.mjs +492 -0
  76. package/scripts/install.mjs +16 -0
  77. package/scripts/smoke-package-closure.mjs +110 -0
  78. package/scripts/smoke-packed-install.mjs +24 -11
  79. package/scripts/smoke-pi-extension.mjs +144 -0
  80. package/scripts/uninstall.mjs +44 -0
  81. package/CHANGELOG.md +0 -405
  82. package/docs/DETERMINISTIC-TOOLS.md +0 -105
  83. package/docs/ENGINEERING-DESIGN.md +0 -194
  84. package/docs/EVALS.md +0 -158
  85. package/docs/GITHUB-RULESET.md +0 -50
  86. package/docs/NPM-PUBLISH.md +0 -116
  87. package/docs/RESEARCH-SOURCES.md +0 -37
  88. package/docs/TRACE-SCHEMA.md +0 -122
  89. package/docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md +0 -75
  90. package/docs/V11-PERCEPTION-ADAPTIVE.md +0 -220
  91. package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +0 -27
  92. package/docs/V13-PARALLEL-WEAK-MODEL-RUNTIME.md +0 -75
  93. package/docs/V7-INTELLIGENCE-RUNTIME.md +0 -166
  94. package/docs/V8-INTELLIGENCE-RELIABILITY.md +0 -206
  95. package/docs/V9-SPEED-INTELLIGENCE.md +0 -102
@@ -29,6 +29,60 @@ export function normalizeUesPromptPaste(text) {
29
29
  return value
30
30
  }
31
31
 
32
+ export function policySourceForPromptAlias(promptAlias, promptText) {
33
+ if (!promptAlias) return normalizeUesPromptPaste(promptText).trim()
34
+
35
+ const args = String(promptAlias.arguments || "").trim()
36
+ if (promptAlias.alias === "ues-resume") {
37
+ return [
38
+ "Resume an existing durable UES execution.",
39
+ args,
40
+ ].filter(Boolean).join("\n")
41
+ }
42
+
43
+ return args || `/${promptAlias.alias}`
44
+ }
45
+
46
+ export function promptAliasTextForPolicy(promptAlias, policy) {
47
+ if (!promptAlias) return null
48
+ if (promptAlias.alias !== "ues-run") return promptAlias.text
49
+
50
+ const args = String(promptAlias.arguments || "").trim()
51
+ const mode = String(policy?.mode || "").toLowerCase()
52
+ const risk = String(policy?.risk || "").toLowerCase()
53
+ const profile = String(policy?.executionProfile || policy?.profile?.name || "").toLowerCase()
54
+ const isDeep = mode === "long-horizon" || risk === "high" || profile === "deep"
55
+ const isFast = !isDeep && (profile === "fast" || mode === "inline")
56
+ const isStandard = !isDeep && (profile === "standard" || mode === "standard")
57
+
58
+ if (isFast) {
59
+ return [
60
+ "UES V2 prompt alias: /ues-run. Adaptive policy selected FAST from the actual user request.",
61
+ "Preserve the user's exact requested outcome and response constraints.",
62
+ "Do not initialize .ues-work, create SPEC/PLAN state, inspect the repository, dispatch subagents, or run verification unless the request itself requires repository work or machine-checkable evidence.",
63
+ "If the request is directly answerable, answer it directly now.",
64
+ "",
65
+ "User request:",
66
+ args,
67
+ ].join("\n").trim()
68
+ }
69
+
70
+ if (isStandard) {
71
+ return [
72
+ "UES V2 prompt alias: /ues-run. Adaptive policy selected STANDARD from the actual user request.",
73
+ "Preserve the user's exact requested outcome and approval boundaries.",
74
+ "Use targeted repository evidence, bounded edits, and targeted + affected verification.",
75
+ "Do not create durable .ues-work state, plan gates, integration gates, or parallel workers unless new evidence justifies reclassification to DEEP/high-risk.",
76
+ "If evidence materially widens scope or risk, re-run ocskill task-policy on a concise updated summary before escalating.",
77
+ "",
78
+ "User request:",
79
+ args,
80
+ ].join("\n").trim()
81
+ }
82
+
83
+ return promptAlias.text
84
+ }
85
+
32
86
  export function policyPromptForCli(text, maxChars = 12_000) {
33
87
  const raw = normalizeUesPromptPaste(text).trim()
34
88
  const limit = Math.max(2_000, Number(maxChars) || 12_000)
@@ -8,7 +8,13 @@ import { runtimeCapabilities } from "./capabilities.js"
8
8
  import { parallelRootBaseline, runEventDrivenDAG } from "./parallel-runtime.js"
9
9
  import { readProjectJson, readProjectText } from "./text-runtime.js"
10
10
  import { extractVerifierVerdict } from "./verifier-runtime.js"
11
- import { expandUesPromptAlias, policyPromptForCli } from "./command-runtime.js"
11
+ import {
12
+ expandUesPromptAlias,
13
+ policyPromptForCli,
14
+ policySourceForPromptAlias,
15
+ promptAliasTextForPolicy,
16
+ } from "./command-runtime.js"
17
+ import { classifyEngineeringTask } from "./policy-runtime.js"
12
18
  import {
13
19
  budgetToolResult,
14
20
  classifyProviderFailure,
@@ -51,6 +57,13 @@ function spawnHidden(command, args, options = {}) {
51
57
  })
52
58
  }
53
59
 
60
+ function bestEffortProgress(tool, status) {
61
+ try {
62
+ const pending = tool?.progress?.({ status })
63
+ if (pending && typeof pending.catch === "function") pending.catch(() => {})
64
+ } catch {}
65
+ }
66
+
54
67
  function findWindowsCommand(name) {
55
68
  const result = spawnHidden("where", [name], { encoding: "utf8" })
56
69
  if (result.status !== 0 || !result.stdout) return null
@@ -566,7 +579,7 @@ export default {
566
579
  },
567
580
  options: { namespace: "ues", codemode: true },
568
581
  execute: async (input) => ({
569
- content: runOcskill(["task-policy", input.text], projectRoot),
582
+ content: JSON.stringify(classifyEngineeringTask(input.text), null, 2),
570
583
  }),
571
584
  })
572
585
  editor.add({
@@ -975,7 +988,7 @@ export default {
975
988
  ...(started?.contextPack?.task?.acceptance || []),
976
989
  started?.contextPack?.task?.risk ? "risk: " + started.contextPack.task.risk : null,
977
990
  ].filter(Boolean).join(" ")
978
- const taskPolicy = runOcskillJSON(["task-policy", taskText], projectRoot)
991
+ const taskPolicy = classifyEngineeringTask(taskText)
979
992
  appendTrace(traceID, "dispatch.started", {
980
993
  slug: input.slug,
981
994
  task: input.task,
@@ -1473,7 +1486,7 @@ export default {
1473
1486
  if (result.sandbox?.dir) {
1474
1487
  try { runOcskill(["sandbox", "remove", result.sandbox.dir, projectRoot, "--force", "--delete-branch"], projectRoot) } catch {}
1475
1488
  }
1476
- try { void tool.progress({ status: "parallel task " + task.id + " verified and integrated" }) } catch {}
1489
+ bestEffortProgress(tool, "parallel task " + task.id + " verified and integrated")
1477
1490
  return { integration, deterministicReceipts, receipt, completed }
1478
1491
  } catch (error) {
1479
1492
  if (!durableCompleted) {
@@ -1489,7 +1502,7 @@ export default {
1489
1502
  },
1490
1503
  onEvent: (event) => {
1491
1504
  if (["task.started", "task.completed", "task.failed"].includes(event.type)) {
1492
- void tool.progress({ status: "parallel " + event.type + " " + event.task })
1505
+ bestEffortProgress(tool, "parallel " + event.type + " " + event.task)
1493
1506
  }
1494
1507
  },
1495
1508
  })
@@ -1538,9 +1551,17 @@ export default {
1538
1551
  const config = routerConfig()
1539
1552
  if (!config.enabled) return
1540
1553
 
1541
- const promptAlias = expandUesPromptAlias(event.prompt?.text, COMMAND_TEMPLATE_DIR)
1554
+ const originalPromptText = String(event.prompt?.text || "")
1555
+ const promptAlias = expandUesPromptAlias(originalPromptText, COMMAND_TEMPLATE_DIR)
1556
+ const routingText = promptAlias
1557
+ ? (promptAlias.arguments || `/${promptAlias.alias}`)
1558
+ : originalPromptText
1559
+ const policySource = policySourceForPromptAlias(promptAlias, originalPromptText)
1560
+ const policyInput = policyPromptForCli(policySource)
1561
+ const policy = classifyEngineeringTask(policyInput.text)
1562
+
1542
1563
  if (promptAlias && event.prompt) {
1543
- event.prompt.text = promptAlias.text
1564
+ event.prompt.text = promptAliasTextForPolicy(promptAlias, policy)
1544
1565
  event.metadata = {
1545
1566
  ...event.metadata,
1546
1567
  uesPromptAlias: {
@@ -1548,27 +1569,12 @@ export default {
1548
1569
  sourceName: promptAlias.sourceName,
1549
1570
  preferredAgent: promptAlias.agent,
1550
1571
  transport: "session.prompt",
1572
+ adaptiveProfile: policy?.executionProfile || policy?.profile?.name || null,
1551
1573
  },
1552
1574
  }
1553
1575
  }
1554
1576
 
1555
- const promptText = String(event.prompt?.text || "")
1556
- const routingText = promptAlias
1557
- ? (promptAlias.arguments || `/${promptAlias.alias}`)
1558
- : promptText
1559
- const policySource = promptAlias
1560
- ? [
1561
- ["ues-run", "ues-resume"].includes(promptAlias.alias)
1562
- ? "Explicit long-horizon engineering request."
1563
- : `UES command /${promptAlias.alias}.`,
1564
- promptAlias.arguments,
1565
- ].filter(Boolean).join("\n")
1566
- : routingText
1567
- const policyInput = policyPromptForCli(policySource)
1568
- let policy = null
1569
- try {
1570
- policy = runOcskillJSON(["task-policy", policyInput.text], projectRoot)
1571
- } catch {}
1577
+ const promptText = String(event.prompt?.text || originalPromptText)
1572
1578
  const intent = classifyIntent(routingText, routingFacts)
1573
1579
  const effectiveMaxSkills = Math.max(
1574
1580
  1,
@@ -0,0 +1,7 @@
1
+ // Legacy OpenCode router compatibility shim.
2
+ // The canonical task policy is Pi-native and lives in lib/task-policy.mjs.
3
+ // Keep this file only so older installations do not fork policy behavior.
4
+ export {
5
+ classifyEngineeringTask,
6
+ recoveryPolicyForAttempt,
7
+ } from "../../../lib/task-policy.mjs"
@@ -2,6 +2,16 @@ function add(list, id) {
2
2
  if (!list.includes(id)) list.push(id)
3
3
  }
4
4
 
5
+ const HIGH_RISK_MUTATION = /((?:fix|change|modify|update|alter|migrate|drop|truncate|delete|remove|rotate|deploy|publish|push|sửa|thay đổi|cập nhật|xóa|xoá|di trú|chuyển đổi|triển khai).{0,64}(?:\bauth\b|authorization|authentication|security|permission|payment(?: handling| flow)?|schema|database|production|public api|secret|credential|phân quyền|bảo mật|thanh toán|cơ sở dữ liệu|api công khai|bí mật|thông tin xác thực)|(?:\bauth\b|authorization|authentication|security|permission|payment(?: handling| flow)?|schema|database|production|public api|secret|credential|phân quyền|bảo mật|thanh toán|cơ sở dữ liệu|api công khai|bí mật|thông tin xác thực).{0,64}(?:fix|change|modify|update|alter|migrate|drop|truncate|delete|remove|rotate|deploy|publish|push|sửa|thay đổi|cập nhật|xóa|xoá|di trú|chuyển đổi|triển khai)|database migration|schema migration|migrate database|migrate schema|drop table|truncate table|deploy(?:ment)?\s+(?:to\s+)?production|production\s+deploy(?:ment)?|rotate\s+(?:secret|credential)|breaking\s+(?:change\s+to\s+)?(?:public\s+)?api|npm publish|git push|force push|reset --hard|git clean)/i
6
+
7
+ function riskTextFor(value) {
8
+ return String(value || "")
9
+ .replace(/\b(?:do not|don't|without)\s+(?:edit|modify|change|write|delete|remove)[^.\n]*/gi, "")
10
+ .replace(/\b(?:no|read[- ]only)\s+(?:edits?|changes?|writes?)[^.\n]*/gi, "")
11
+ .replace(/không\s+(?:sửa|chỉnh sửa|thay đổi|ghi|xóa|xoá)[^.\n]*/gi, "")
12
+ .replace(/chỉ\s+đọc[^.\n]*/gi, "")
13
+ }
14
+
5
15
  const PROCESS_SKILLS = new Set([
6
16
  "ues-engineering-orchestrator",
7
17
  "ues-long-task-state",
@@ -90,8 +100,8 @@ export function classifyIntent(text, facts = {}) {
90
100
  if (/(refactor|cleanup|restructure|refactor toàn bộ)/.test(value)) add(actions, "refactor")
91
101
  if (/(latest|current docs|documentation|release notes|version compatibility|dependency|package version|api changed|tài liệu mới nhất|phiên bản mới|tương thích phiên bản|package mới)/.test(value)) add(actions, "research")
92
102
 
93
- const risky = /(migration|schema|database|sql|\bauth\b|authorization|authentication|permission|security|payment|webhook|public api|contract|dependency|deploy|\bci\b|production|rollback|cơ sở dữ liệu|phân quyền|xác thực|bảo mật|thanh toán|triển khai|phụ thuộc)/.test(value)
94
- const longHorizon = value.length > 700 || /(large task|big task|long[- ]running|multi[- ]file|cross[- ]module|whole (?:repo|repository|project)|entire (?:repo|repository|project)|full refactor|refactor all|migrate all|resume this work|toàn bộ (?:repo|repository|dự án)|nhiều file|nhiều module|refactor toàn bộ|tiếp tục công việc)/.test(value)
103
+ const risky = HIGH_RISK_MUTATION.test(riskTextFor(value))
104
+ const longHorizon = value.length > 700 || /(large task|big task|long[- ]running|multi[- ]file|cross[- ]module|whole (?:repo|repository|project)|entire (?:repo|repository|project)|full refactor|refactor all|migrate all|multi[- ]step migration|migration across|resume this work|toàn bộ (?:repo|repository|dự án)|nhiều file|nhiều module|refactor toàn bộ|tiếp tục công việc)/.test(value)
95
105
  const nonTrivial = value.length > 220 || risky || actions.length > 0
96
106
 
97
107
  const feedbackDomains = [...new Set((facts.feedbackDomains || []).filter((item) => domains.includes(item)))]
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ecommerce-engineering
3
- description: Build/review ecommerce and marketplace systems: catalog, sellers, carts, checkout, orders, inventory, pricing, images, and traceability.
3
+ description: "Build/review ecommerce and marketplace systems: catalog, sellers, carts, checkout, orders, inventory, pricing, images, and traceability."
4
4
  ---
5
5
 
6
6
  # Ecommerce Engineering
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: file-upload-engineering
3
- description: Implement file/image uploads safely: validation, storage, naming, URLs, cleanup, permissions, progress, and errors.
3
+ description: "Implement file/image uploads safely: validation, storage, naming, URLs, cleanup, permissions, progress, and errors."
4
4
  ---
5
5
 
6
6
  # File Upload Engineering
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: git-safety
3
- description: Use Git safely: inspect status/diffs, preserve user work, stage intentional files, commit clearly, and avoid destructive history operations.
3
+ description: "Use Git safely: inspect status/diffs, preserve user work, stage intentional files, commit clearly, and avoid destructive history operations."
4
4
  ---
5
5
 
6
6
  # Git Safety
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: nestjs-engineering
3
- description: Work on NestJS apps: modules, controllers, providers, DTO validation, guards, interceptors, persistence, and tests.
3
+ description: "Work on NestJS apps: modules, controllers, providers, DTO validation, guards, interceptors, persistence, and tests."
4
4
  ---
5
5
 
6
6
  # Nestjs Engineering
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: performance-engineering
3
- description: Diagnose and improve performance using evidence: rendering, database, network, memory, caching, bundles, concurrency, and hot paths.
3
+ description: "Diagnose and improve performance using evidence: rendering, database, network, memory, caching, bundles, concurrency, and hot paths."
4
4
  ---
5
5
 
6
6
  # Performance Engineering
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: react-native-engineering
3
- description: Work on React Native/Expo apps: components, hooks, navigation, styling, APIs, native modules, Android/iOS build failures, platform behavior, and performance with version-aware verification.
3
+ description: "Work on React Native/Expo apps: components, hooks, navigation, styling, APIs, native modules, Android/iOS build failures, platform behavior, and performance with version-aware verification."
4
4
  ---
5
5
 
6
6
  # React Native Engineering
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: rest-api-design
3
- description: Design/review REST APIs: resources, methods, status codes, pagination, validation, errors, versioning, and idempotency.
3
+ description: "Design/review REST APIs: resources, methods, status codes, pagination, validation, errors, versioning, and idempotency."
4
4
  ---
5
5
 
6
6
  # Rest Api Design
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ui-ux-engineering
3
- description: Improve production UI/UX: hierarchy, responsive layout, components, forms, states, accessibility, realistic content, and design consistency.
3
+ description: "Improve production UI/UX: hierarchy, responsive layout, components, forms, states, accessibility, realistic content, and design consistency."
4
4
  ---
5
5
 
6
6
  # Ui Ux Engineering
@@ -0,0 +1,97 @@
1
+ const ROLE_TARGETS = Object.freeze({
2
+ fast: {
3
+ architect: 8_000,
4
+ "plan-checker": 6_000,
5
+ executor: 6_000,
6
+ debugger: 8_000,
7
+ verifier: 4_500,
8
+ "integration-verifier": 8_000,
9
+ "visual-verifier": 5_000,
10
+ reviewer: 5_000,
11
+ critic: 6_000,
12
+ "codebase-mapper": 6_000,
13
+ researcher: 6_000,
14
+ "merge-arbiter": 8_000,
15
+ },
16
+ standard: {
17
+ architect: 12_000,
18
+ "plan-checker": 10_000,
19
+ executor: 11_000,
20
+ debugger: 14_000,
21
+ verifier: 8_000,
22
+ "integration-verifier": 12_000,
23
+ "visual-verifier": 8_000,
24
+ reviewer: 8_000,
25
+ critic: 10_000,
26
+ "codebase-mapper": 10_000,
27
+ researcher: 10_000,
28
+ "merge-arbiter": 12_000,
29
+ },
30
+ deep: {
31
+ architect: 30_000,
32
+ "plan-checker": 24_000,
33
+ executor: 26_000,
34
+ debugger: 32_000,
35
+ verifier: 20_000,
36
+ "integration-verifier": 30_000,
37
+ "visual-verifier": 18_000,
38
+ reviewer: 20_000,
39
+ critic: 24_000,
40
+ "codebase-mapper": 28_000,
41
+ researcher: 24_000,
42
+ "merge-arbiter": 30_000,
43
+ },
44
+ })
45
+
46
+ function clamp(value, fallback, min, max) {
47
+ const parsed = Number(value)
48
+ if (!Number.isFinite(parsed)) return fallback
49
+ return Math.max(min, Math.min(max, Math.trunc(parsed)))
50
+ }
51
+
52
+ export function adaptiveContextBudget(taskPolicy = {}, role = "executor", attempt = 1, options = {}) {
53
+ const base = clamp(
54
+ taskPolicy.contextBudget ?? taskPolicy.profile?.contextBudget,
55
+ 20_000,
56
+ 4_000,
57
+ 48_000,
58
+ )
59
+ const profile = String(taskPolicy.executionProfile || taskPolicy.profile?.name || "standard")
60
+ const highRisk = taskPolicy.risk === "high" || taskPolicy.risk === "critical"
61
+
62
+ // High-risk work keeps the original evidence budget. Turbo mode must not trade
63
+ // away security/payment/schema evidence for latency.
64
+ if (highRisk || options.disabled === true) {
65
+ return {
66
+ schemaVersion: 1,
67
+ budget: base,
68
+ baseBudget: base,
69
+ adaptive: false,
70
+ reason: highRisk ? "high-risk-preserves-base-budget" : "disabled",
71
+ }
72
+ }
73
+
74
+ const table = ROLE_TARGETS[profile] || ROLE_TARGETS.standard
75
+ const target = clamp(table?.[role], base, 4_000, base)
76
+ const normalizedAttempt = Math.max(1, Math.trunc(Number(attempt || 1)))
77
+
78
+ // Failed attempts expand deterministically back toward the policy ceiling.
79
+ let budget = target
80
+ if (normalizedAttempt === 2) budget = Math.min(base, Math.max(target, Math.round(target * 1.5)))
81
+ else if (normalizedAttempt >= 3) budget = base
82
+
83
+ if (options.contextInsufficient === true) {
84
+ budget = Math.min(base, Math.max(budget, Math.round(target * 1.75)))
85
+ }
86
+
87
+ return {
88
+ schemaVersion: 1,
89
+ budget,
90
+ baseBudget: base,
91
+ adaptive: budget < base,
92
+ profile,
93
+ role,
94
+ attempt: normalizedAttempt,
95
+ reason: budget < base ? "role-bounded-expand-on-failure" : "policy-ceiling",
96
+ }
97
+ }
@@ -0,0 +1,260 @@
1
+ import { existsSync } from "node:fs"
2
+ import { readFile, readdir, stat } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { spawnSync } from "node:child_process"
5
+ import { runtimeWorkspaceFingerprint } from "./workspace-fingerprint.mjs"
6
+
7
+ const TEST_RE = /(?:^|[/.\\])(?:__tests__|tests?|spec)(?:[/.\\]|$)|\.(?:test|spec|e2e-spec)\.[cm]?[jt]sx?$/i
8
+ const SOURCE_EXT = new Set([".js",".jsx",".mjs",".cjs",".ts",".tsx",".py",".java",".kt",".kts",".cs",".go",".rs",".dart",".swift"])
9
+ const SKIP = new Set([".git","node_modules",".next","dist","build","coverage",".venv","venv","target","Pods","DerivedData",".gradle",".ues-cache",".ues-work",".ues-sandboxes",".ues-traces",".ues-learning",".ues-dashboard",".ues-memory",".ues-evals"])
10
+ const AFFECTED_TEST_CACHE = new Map()
11
+ const AFFECTED_TEST_INFLIGHT = new Map()
12
+
13
+ function git(root, args) {
14
+ return spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 })
15
+ }
16
+
17
+ function normalize(file) {
18
+ return String(file || "").replaceAll("\\", "/").replace(/^\.\//, "")
19
+ }
20
+
21
+ function stem(file) {
22
+ return path.basename(file)
23
+ .replace(/\.(?:test|spec|e2e-spec)(?=\.)/i, "")
24
+ .replace(/\.[^.]+$/, "")
25
+ .toLowerCase()
26
+ }
27
+
28
+ function tokens(file) {
29
+ return [...new Set(normalize(file).toLowerCase().split(/[^a-z0-9]+/).filter((x) => x.length >= 3))]
30
+ }
31
+
32
+ async function walk(root, options = {}) {
33
+ const maxFiles = Math.max(200, Number(options.maxFiles || 6000))
34
+ const rows = []
35
+ async function visit(dir, depth) {
36
+ if (rows.length >= maxFiles || depth > 14) return
37
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
38
+ for (const entry of entries) {
39
+ if (rows.length >= maxFiles) break
40
+ if (entry.isDirectory() && SKIP.has(entry.name)) continue
41
+ const full = path.join(dir, entry.name)
42
+ if (entry.isDirectory()) await visit(full, depth + 1)
43
+ else if (entry.isFile()) rows.push(full)
44
+ }
45
+ }
46
+ await visit(root, 0)
47
+ return rows
48
+ }
49
+
50
+ export function gitChangedFiles(root = process.cwd()) {
51
+ root = path.resolve(root)
52
+ const inside = git(root, ["rev-parse", "--is-inside-work-tree"])
53
+ if (inside.status !== 0) return []
54
+
55
+ const names = new Set()
56
+ for (const args of [
57
+ ["diff", "--name-only", "--"],
58
+ ["diff", "--cached", "--name-only", "--"],
59
+ ["ls-files", "--others", "--exclude-standard"],
60
+ ]) {
61
+ const result = git(root, args)
62
+ if (result.status !== 0) continue
63
+ for (const row of String(result.stdout || "").split(/\r?\n/)) {
64
+ const file = normalize(row.trim())
65
+ if (file) names.add(file)
66
+ }
67
+ }
68
+ return [...names]
69
+ }
70
+
71
+ function scoreTest(testFile, changedFile, content = "") {
72
+ const test = normalize(testFile).toLowerCase()
73
+ const changed = normalize(changedFile).toLowerCase()
74
+ let score = 0
75
+ const reasons = []
76
+ const changedStem = stem(changed)
77
+ const testStem = stem(test)
78
+
79
+ if (changedStem && testStem.includes(changedStem)) {
80
+ score += 35
81
+ reasons.push("same-stem")
82
+ }
83
+ const testDir = path.posix.dirname(test)
84
+ const changedDir = path.posix.dirname(changed)
85
+ if (testDir === changedDir) {
86
+ score += 20
87
+ reasons.push("same-directory")
88
+ } else if (testDir.startsWith(changedDir + "/") || changedDir.startsWith(testDir + "/")) {
89
+ score += 10
90
+ reasons.push("nearby-directory")
91
+ }
92
+
93
+ const changedTokens = tokens(changed)
94
+ const overlap = changedTokens.filter((token) => test.includes(token)).length
95
+ if (overlap) {
96
+ score += Math.min(20, overlap * 4)
97
+ reasons.push("path-token-overlap")
98
+ }
99
+
100
+ const base = path.basename(changed).replace(/\.[^.]+$/, "")
101
+ if (base.length >= 3 && content.toLowerCase().includes(base.toLowerCase())) {
102
+ score += 25
103
+ reasons.push("content-reference")
104
+ }
105
+
106
+ return { score, reasons }
107
+ }
108
+
109
+ async function nearestPackage(root, file) {
110
+ let dir = path.dirname(path.resolve(root, file))
111
+ const base = path.resolve(root)
112
+ while (dir === base || dir.startsWith(base + path.sep)) {
113
+ const packageFile = path.join(dir, "package.json")
114
+ if (existsSync(packageFile)) {
115
+ try {
116
+ return { dir, json: JSON.parse(await readFile(packageFile, "utf8")) }
117
+ } catch {}
118
+ }
119
+ if (dir === base) break
120
+ dir = path.dirname(dir)
121
+ }
122
+ return null
123
+ }
124
+
125
+ function packageManager(root) {
126
+ if (existsSync(path.join(root, "pnpm-lock.yaml"))) return "pnpm"
127
+ if (existsSync(path.join(root, "yarn.lock"))) return "yarn"
128
+ if (existsSync(path.join(root, "bun.lock")) || existsSync(path.join(root, "bun.lockb"))) return "bun"
129
+ return "npm"
130
+ }
131
+
132
+ function targetedCommand(root, pkg, testFile) {
133
+ const script = String(pkg?.json?.scripts?.test || "")
134
+ if (!script || !/(jest|vitest|node\s+--test|tsx\s+--test)/i.test(script)) return null
135
+ const manager = packageManager(root)
136
+ const relativeDir = normalize(path.relative(root, pkg.dir)) || "."
137
+ const relativeTest = normalize(path.relative(pkg.dir, path.resolve(root, testFile)))
138
+
139
+ if (manager === "pnpm") {
140
+ return { command: "pnpm", args: ["--dir", relativeDir, "run", "test", "--", relativeTest], confidence: "high" }
141
+ }
142
+ if (manager === "yarn") {
143
+ return { command: "yarn", args: ["--cwd", relativeDir, "test", relativeTest], confidence: "high" }
144
+ }
145
+ if (manager === "bun") {
146
+ return { command: "bun", args: ["--cwd", relativeDir, "run", "test", "--", relativeTest], confidence: "high" }
147
+ }
148
+ return { command: "npm", args: ["--prefix", relativeDir, "test", "--", relativeTest], confidence: "high" }
149
+ }
150
+
151
+ export async function resolveAffectedTests(root = process.cwd(), options = {}) {
152
+ root = path.resolve(root)
153
+ const changed = (options.changedFiles || gitChangedFiles(root))
154
+ .map(normalize)
155
+ .filter((file) => SOURCE_EXT.has(path.extname(file).toLowerCase()) && !TEST_RE.test(file))
156
+
157
+ if (!changed.length) {
158
+ return {
159
+ schemaVersion: 1,
160
+ root,
161
+ changedFiles: [],
162
+ tests: [],
163
+ suggestedCommands: [],
164
+ truncated: false,
165
+ cacheHit: false,
166
+ }
167
+ }
168
+
169
+ const limit = Math.max(1, Math.min(40, Number(options.limit || 12)))
170
+ let fingerprint = String(options.workspaceFingerprint || "")
171
+ if (!fingerprint || fingerprint === "unknown") {
172
+ try { fingerprint = runtimeWorkspaceFingerprint(root) } catch { fingerprint = "" }
173
+ }
174
+ if (fingerprint === "unknown") fingerprint = ""
175
+
176
+ const cacheKey = JSON.stringify([
177
+ root,
178
+ fingerprint || "no-fingerprint",
179
+ [...changed].sort(),
180
+ Number(options.maxFiles || 6000),
181
+ limit,
182
+ ])
183
+
184
+ if (fingerprint && AFFECTED_TEST_CACHE.has(cacheKey)) {
185
+ return { ...AFFECTED_TEST_CACHE.get(cacheKey), cacheHit: true }
186
+ }
187
+ if (fingerprint && AFFECTED_TEST_INFLIGHT.has(cacheKey)) {
188
+ const value = await AFFECTED_TEST_INFLIGHT.get(cacheKey)
189
+ return { ...value, cacheHit: true, cacheCoalesced: true }
190
+ }
191
+
192
+ const compute = async () => {
193
+ const files = await walk(root, options)
194
+ const tests = files
195
+ .map((file) => normalize(path.relative(root, file)))
196
+ .filter((file) => TEST_RE.test(file))
197
+
198
+ const ranked = []
199
+ for (const testFile of tests) {
200
+ const info = await stat(path.join(root, testFile)).catch(() => null)
201
+ const content = info?.size && info.size <= 512 * 1024
202
+ ? await readFile(path.join(root, testFile), "utf8").catch(() => "")
203
+ : ""
204
+ let best = { score: 0, reasons: [], changedFile: null }
205
+ for (const changedFile of changed) {
206
+ const current = scoreTest(testFile, changedFile, content)
207
+ if (current.score > best.score) best = { ...current, changedFile }
208
+ }
209
+ if (best.score > 0) ranked.push({ path: testFile, ...best })
210
+ }
211
+
212
+ ranked.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
213
+ const selected = ranked.slice(0, limit)
214
+ const suggestedCommands = []
215
+ const seen = new Set()
216
+
217
+ for (const item of selected.slice(0, 8)) {
218
+ const pkg = await nearestPackage(root, item.path)
219
+ const command = pkg ? targetedCommand(root, pkg, item.path) : null
220
+ if (!command) continue
221
+ const key = JSON.stringify([command.command, command.args])
222
+ if (seen.has(key)) continue
223
+ seen.add(key)
224
+ suggestedCommands.push({ ...command, test: item.path })
225
+ }
226
+
227
+ return {
228
+ schemaVersion: 1,
229
+ root,
230
+ changedFiles: changed,
231
+ tests: selected,
232
+ suggestedCommands,
233
+ truncated: ranked.length > selected.length,
234
+ cacheHit: false,
235
+ }
236
+ }
237
+
238
+ const pending = compute()
239
+ if (fingerprint) AFFECTED_TEST_INFLIGHT.set(cacheKey, pending)
240
+
241
+ try {
242
+ const result = await pending
243
+ if (fingerprint) {
244
+ AFFECTED_TEST_CACHE.set(cacheKey, result)
245
+ while (AFFECTED_TEST_CACHE.size > 24) {
246
+ const oldest = AFFECTED_TEST_CACHE.keys().next().value
247
+ if (!oldest) break
248
+ AFFECTED_TEST_CACHE.delete(oldest)
249
+ }
250
+ }
251
+ return result
252
+ } finally {
253
+ if (fingerprint) AFFECTED_TEST_INFLIGHT.delete(cacheKey)
254
+ }
255
+ }
256
+
257
+ export function clearAffectedTestCache() {
258
+ AFFECTED_TEST_CACHE.clear()
259
+ AFFECTED_TEST_INFLIGHT.clear()
260
+ }