opencode-agent-skill 13.0.0-beta.2 → 14.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1571 -607
- package/bin/ocskill.mjs +172 -24
- package/docs/OPENCODE-COMPAT.md +34 -97
- package/docs/PI-COMPAT.md +203 -0
- package/docs/V14-CONTEXT-MEMORY-FABRIC.md +70 -0
- package/docs/V14.1-QUALITY-PERFORMANCE-FABRIC.md +114 -0
- package/docs/V14.2-TURBO-WEAK-MODEL-RUNTIME.md +448 -0
- package/evals/v14/tasks.json +46 -0
- package/global-config/agents/executor.md +7 -0
- package/global-config/agents/visual-verifier.md +22 -3
- package/global-config/plugins/ues-router/index.js +13 -8
- package/global-config/plugins/ues-router/policy-runtime.js +7 -0
- package/global-config/plugins/ues-router/router.js +12 -2
- package/global-config/skills/ecommerce-engineering/SKILL.md +1 -1
- package/global-config/skills/file-upload-engineering/SKILL.md +1 -1
- package/global-config/skills/git-safety/SKILL.md +1 -1
- package/global-config/skills/nestjs-engineering/SKILL.md +1 -1
- package/global-config/skills/performance-engineering/SKILL.md +1 -1
- package/global-config/skills/react-native-engineering/SKILL.md +1 -1
- package/global-config/skills/rest-api-design/SKILL.md +1 -1
- package/global-config/skills/ui-ux-engineering/SKILL.md +1 -1
- package/lib/adaptive-context-budget.mjs +97 -0
- package/lib/affected-tests.mjs +260 -0
- package/lib/benchmark-confidence.mjs +41 -2
- package/lib/browser-mcp-routing.mjs +166 -0
- package/lib/capability-fabric.mjs +359 -0
- package/lib/capability-registry.mjs +9 -0
- package/lib/code-intelligence/edit-anchor.mjs +102 -0
- package/lib/code-intelligence/index.mjs +95 -0
- package/lib/code-intelligence/lsp-provider.mjs +189 -0
- package/lib/completion-auditor.mjs +82 -0
- package/lib/context-engine-v11.mjs +65 -1
- package/lib/context-graph-rank.mjs +118 -0
- package/lib/context-manifest.mjs +97 -18
- package/lib/control-center.mjs +19 -1
- package/lib/document-ingestion.mjs +60 -0
- package/lib/dynamic-workflow.mjs +3 -1
- package/lib/evidence-store.mjs +82 -1
- package/lib/fast-verification-gate.mjs +69 -0
- package/lib/hierarchical-context.mjs +215 -0
- package/lib/mcp-health.mjs +144 -0
- package/lib/mcp-tool-policy.mjs +19 -0
- package/lib/memory-engine.mjs +493 -0
- package/lib/model-performance.mjs +33 -8
- package/lib/model-policy.mjs +3 -3
- package/lib/orchestrator-policy.mjs +5 -209
- package/lib/performance-fabric.mjs +229 -0
- package/lib/pi-rpc-pool.mjs +433 -0
- package/lib/process-hang-detector.mjs +83 -0
- package/lib/process-supervisor.mjs +193 -0
- package/lib/prompt-cache.mjs +27 -11
- package/lib/repo-graph.mjs +53 -2
- package/lib/reversible-context.mjs +49 -0
- package/lib/runtime-config.mjs +31 -0
- package/lib/safety.mjs +132 -0
- package/lib/semantic-index.mjs +52 -3
- package/lib/skill-compiler.mjs +128 -0
- package/lib/skill-quality.mjs +48 -2
- package/lib/task-engine.mjs +62 -5
- package/lib/task-policy.mjs +291 -0
- package/lib/verification-broker.mjs +284 -0
- package/lib/verification-command.mjs +111 -0
- package/lib/windows-shim.mjs +35 -0
- package/lib/workspace-fingerprint.mjs +198 -0
- package/package.json +52 -42
- package/pi/extensions/ues-child-runtime.ts +393 -0
- package/pi/extensions/ues.ts +3327 -0
- package/pi/prompts/ues-audit.md +9 -0
- package/pi/prompts/ues-critique.md +9 -0
- package/pi/prompts/ues-debug.md +9 -0
- package/pi/prompts/ues-feature.md +9 -0
- package/pi/prompts/ues-fix.md +9 -0
- package/pi/prompts/ues-plan.md +9 -0
- package/pi/prompts/ues-research.md +9 -0
- package/pi/prompts/ues-resume.md +9 -0
- package/pi/prompts/ues-review.md +7 -0
- package/pi/prompts/ues-run.md +17 -0
- package/pi/prompts/ues-verify.md +9 -0
- package/scripts/check-release-consistency.mjs +119 -185
- package/scripts/check-runtime-exports.mjs +66 -0
- package/scripts/check-source-integrity.mjs +246 -0
- package/scripts/eval-pi.mjs +492 -0
- package/scripts/install.mjs +16 -0
- package/scripts/smoke-package-closure.mjs +110 -0
- package/scripts/smoke-packed-install.mjs +24 -11
- package/scripts/smoke-pi-extension.mjs +144 -0
- package/scripts/uninstall.mjs +44 -0
- package/CHANGELOG.md +0 -415
- package/docs/DETERMINISTIC-TOOLS.md +0 -105
- package/docs/ENGINEERING-DESIGN.md +0 -194
- package/docs/EVALS.md +0 -158
- package/docs/GITHUB-RULESET.md +0 -50
- package/docs/NPM-PUBLISH.md +0 -116
- package/docs/RESEARCH-SOURCES.md +0 -37
- package/docs/TRACE-SCHEMA.md +0 -122
- package/docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md +0 -75
- package/docs/V11-PERCEPTION-ADAPTIVE.md +0 -220
- package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +0 -27
- package/docs/V13-PARALLEL-WEAK-MODEL-RUNTIME.md +0 -86
- package/docs/V7-INTELLIGENCE-RUNTIME.md +0 -166
- package/docs/V8-INTELLIGENCE-RELIABILITY.md +0 -206
- package/docs/V9-SPEED-INTELLIGENCE.md +0 -102
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
3
|
+
import { spawnSync } from "node:child_process"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
|
|
6
|
+
const HEALTH_WEIGHT = { healthy: 40, degraded: 15, unknown: 0, unavailable: -1000 }
|
|
7
|
+
const COST_PENALTY = { low: 0, medium: 5, high: 12 }
|
|
8
|
+
const LATENCY_PENALTY = { fast: 0, medium: 3, slow: 8 }
|
|
9
|
+
const OBSERVATION_FILE = "CAPABILITY-OBSERVATIONS.json"
|
|
10
|
+
|
|
11
|
+
function observationPath(root) {
|
|
12
|
+
return path.join(path.resolve(root), ".ues-learning", OBSERVATION_FILE)
|
|
13
|
+
}
|
|
14
|
+
async function atomicJson(file, value) {
|
|
15
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
16
|
+
const temp = file + "." + process.pid + "." + Date.now() + ".tmp"
|
|
17
|
+
await writeFile(temp, JSON.stringify(value, null, 2) + "\n", "utf8")
|
|
18
|
+
try { await rename(temp, file) } catch (error) { await rm(temp, { force: true }).catch(() => {}); throw error }
|
|
19
|
+
}
|
|
20
|
+
export async function readCapabilityObservations(root = process.cwd()) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(await readFile(observationPath(root), "utf8"))
|
|
23
|
+
return { schemaVersion: 1, updatedAt: parsed.updatedAt || null, capabilities: parsed.capabilities && typeof parsed.capabilities === "object" ? parsed.capabilities : {} }
|
|
24
|
+
} catch { return { schemaVersion: 1, updatedAt: null, capabilities: {} } }
|
|
25
|
+
}
|
|
26
|
+
export async function recordCapabilityObservation(root, capability, providerId, input = {}) {
|
|
27
|
+
if (!capability || !providerId) throw new Error("capability observation requires capability and provider id")
|
|
28
|
+
const state = await readCapabilityObservations(root)
|
|
29
|
+
const previous = state.capabilities?.[capability]?.[providerId] || {}
|
|
30
|
+
const success = input.success === true, failure = input.success === false
|
|
31
|
+
const samples = Number(previous.samples || 0) + (success || failure ? 1 : 0)
|
|
32
|
+
const successes = Number(previous.successes || 0) + (success ? 1 : 0)
|
|
33
|
+
const failures = Number(previous.failures || 0) + (failure ? 1 : 0)
|
|
34
|
+
const latencyMs = Number.isFinite(Number(input.latencyMs)) ? Math.max(0, Number(input.latencyMs)) : null
|
|
35
|
+
const previousLatencySamples = Number(previous.latencySamples || 0)
|
|
36
|
+
const latencySamples = previousLatencySamples + (latencyMs == null ? 0 : 1)
|
|
37
|
+
const avgLatencyMs = latencyMs == null ? (previous.avgLatencyMs ?? null) : ((Number(previous.avgLatencyMs || 0) * previousLatencySamples) + latencyMs) / Math.max(1, latencySamples)
|
|
38
|
+
const next = {
|
|
39
|
+
samples, successes, failures,
|
|
40
|
+
successRate: samples ? successes / samples : 0,
|
|
41
|
+
failureRate: samples ? failures / samples : 0,
|
|
42
|
+
latencySamples, avgLatencyMs: avgLatencyMs == null ? null : Number(avgLatencyMs.toFixed(2)),
|
|
43
|
+
lastLatencyMs: latencyMs, lastObservedAt: new Date().toISOString(),
|
|
44
|
+
lastError: input.error ? String(input.error).slice(0, 500) : null,
|
|
45
|
+
}
|
|
46
|
+
const capabilities = { ...state.capabilities, [capability]: { ...(state.capabilities?.[capability] || {}), [providerId]: next } }
|
|
47
|
+
await atomicJson(observationPath(root), { schemaVersion: 1, updatedAt: next.lastObservedAt, capabilities })
|
|
48
|
+
return next
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function clamp(value, min = 0, max = 1, fallback = 0) {
|
|
52
|
+
const number = Number(value)
|
|
53
|
+
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : fallback
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const COMMAND_PROBE_TTL_MS = 60_000
|
|
57
|
+
const commandProbeCache = new Map()
|
|
58
|
+
|
|
59
|
+
function commandExists(command) {
|
|
60
|
+
if (!command) return false
|
|
61
|
+
const now = Date.now()
|
|
62
|
+
const cached = commandProbeCache.get(command)
|
|
63
|
+
if (cached && now - cached.checkedAt < COMMAND_PROBE_TTL_MS) return cached.available
|
|
64
|
+
const finder = process.platform === "win32" ? "where" : "which"
|
|
65
|
+
const result = spawnSync(finder, [command], { stdio: "ignore", windowsHide: true })
|
|
66
|
+
const available = result.status === 0
|
|
67
|
+
commandProbeCache.set(command, { available, checkedAt: now })
|
|
68
|
+
return available
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function packageDeclared(root, name) {
|
|
72
|
+
try {
|
|
73
|
+
const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"))
|
|
74
|
+
return Boolean(
|
|
75
|
+
pkg.dependencies?.[name] ||
|
|
76
|
+
pkg.devDependencies?.[name] ||
|
|
77
|
+
pkg.optionalDependencies?.[name] ||
|
|
78
|
+
pkg.peerDependencies?.[name],
|
|
79
|
+
)
|
|
80
|
+
} catch {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeProvider(provider = {}, index = 0) {
|
|
86
|
+
return {
|
|
87
|
+
id: String(provider.id || `provider-${index + 1}`),
|
|
88
|
+
kind: provider.kind || "builtin",
|
|
89
|
+
capability: provider.capability || null,
|
|
90
|
+
priority: Number.isFinite(Number(provider.priority)) ? Number(provider.priority) : 50,
|
|
91
|
+
quality: clamp(provider.quality, 0, 1, 0.5),
|
|
92
|
+
costClass: ["low", "medium", "high"].includes(provider.costClass) ? provider.costClass : "medium",
|
|
93
|
+
latencyClass: ["fast", "medium", "slow"].includes(provider.latencyClass) ? provider.latencyClass : "medium",
|
|
94
|
+
command: provider.command || null,
|
|
95
|
+
path: provider.path || null,
|
|
96
|
+
package: provider.package || null,
|
|
97
|
+
metadata: provider.metadata || {},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function defaultCapabilityRegistry(root = process.cwd()) {
|
|
102
|
+
return {
|
|
103
|
+
schemaVersion: 1,
|
|
104
|
+
root: path.resolve(root),
|
|
105
|
+
capabilities: {
|
|
106
|
+
"code.search": [
|
|
107
|
+
{ id: "ues-semantic-index", kind: "builtin", priority: 100, quality: 0.9, costClass: "low", latencyClass: "fast" },
|
|
108
|
+
],
|
|
109
|
+
"code.edit.anchored": [
|
|
110
|
+
{ id: "ues-hashline", kind: "builtin", priority: 120, quality: 0.98, costClass: "low", latencyClass: "fast", metadata: { failClosed: true, staleAnchorReject: true } },
|
|
111
|
+
],
|
|
112
|
+
"code.structure": [
|
|
113
|
+
{ id: "ast-grep", kind: "command", command: "ast-grep", priority: 120, quality: 0.96, costClass: "low", latencyClass: "fast" },
|
|
114
|
+
{ id: "sg", kind: "command", command: "sg", priority: 115, quality: 0.96, costClass: "low", latencyClass: "fast" },
|
|
115
|
+
{ id: "ues-semantic-index", kind: "builtin", priority: 80, quality: 0.8, costClass: "low", latencyClass: "fast" },
|
|
116
|
+
],
|
|
117
|
+
"code.diagnostics": [
|
|
118
|
+
{ id: "typescript-language-server", kind: "command", command: "typescript-language-server", priority: 110, quality: 0.95, costClass: "low", latencyClass: "medium" },
|
|
119
|
+
{ id: "pyright-langserver", kind: "command", command: "pyright-langserver", priority: 105, quality: 0.95, costClass: "low", latencyClass: "medium" },
|
|
120
|
+
{ id: "pylsp", kind: "command", command: "pylsp", priority: 95, quality: 0.85, costClass: "low", latencyClass: "medium" },
|
|
121
|
+
{ id: "gopls", kind: "command", command: "gopls", priority: 105, quality: 0.95, costClass: "low", latencyClass: "medium" },
|
|
122
|
+
{ id: "rust-analyzer", kind: "command", command: "rust-analyzer", priority: 105, quality: 0.95, costClass: "low", latencyClass: "medium" },
|
|
123
|
+
{ id: "clangd", kind: "command", command: "clangd", priority: 100, quality: 0.9, costClass: "low", latencyClass: "medium" },
|
|
124
|
+
],
|
|
125
|
+
"document.ingestion": [
|
|
126
|
+
{ id: "ues-builtin-text", kind: "builtin", priority: 100, quality: 0.95, costClass: "low", latencyClass: "fast", metadata: { formats: ["text", "markdown", "json", "csv", "html", "xml"] } },
|
|
127
|
+
{ id: "markitdown-cli", kind: "command", command: "markitdown", priority: 95, quality: 0.95, costClass: "low", latencyClass: "medium", metadata: { optional: true, formats: ["pdf", "docx", "pptx", "xlsx", "epub", "zip"] } },
|
|
128
|
+
],
|
|
129
|
+
"context.reversible": [
|
|
130
|
+
{ id: "ues-evidence-context", kind: "builtin", priority: 120, quality: 0.98, costClass: "low", latencyClass: "fast", metadata: { reversible: true, searchable: true, levels: ["T1", "T2", "T3", "raw"] } },
|
|
131
|
+
],
|
|
132
|
+
memory: [
|
|
133
|
+
{ id: "ues-memory", kind: "builtin", priority: 100, quality: 0.9, costClass: "low", latencyClass: "fast" },
|
|
134
|
+
],
|
|
135
|
+
evidence: [
|
|
136
|
+
{ id: "ues-evidence-store", kind: "builtin", priority: 100, quality: 0.95, costClass: "low", latencyClass: "fast" },
|
|
137
|
+
],
|
|
138
|
+
"output.compaction": [
|
|
139
|
+
{
|
|
140
|
+
id: "ues-reversible-compactor",
|
|
141
|
+
kind: "builtin",
|
|
142
|
+
priority: 100,
|
|
143
|
+
quality: 0.98,
|
|
144
|
+
costClass: "low",
|
|
145
|
+
latencyClass: "fast",
|
|
146
|
+
metadata: { default: true, reversible: true, lossy: false, scope: "model-visible UES output" },
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "rtk-cli",
|
|
150
|
+
kind: "command",
|
|
151
|
+
command: "rtk",
|
|
152
|
+
priority: 82,
|
|
153
|
+
quality: 0.9,
|
|
154
|
+
costClass: "low",
|
|
155
|
+
latencyClass: "fast",
|
|
156
|
+
metadata: { default: false, external: true, experimental: false, scope: "shell-output", upstream: "rtk-ai/rtk" },
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "caveman-cli",
|
|
160
|
+
kind: "command",
|
|
161
|
+
command: "caveman",
|
|
162
|
+
priority: 55,
|
|
163
|
+
quality: 0.82,
|
|
164
|
+
costClass: "medium",
|
|
165
|
+
latencyClass: "medium",
|
|
166
|
+
metadata: {
|
|
167
|
+
default: false,
|
|
168
|
+
external: true,
|
|
169
|
+
experimental: true,
|
|
170
|
+
reversible: true,
|
|
171
|
+
upstream: "JuliusBrussee/caveman",
|
|
172
|
+
licenseNote: "External runtime includes BSL-1.1 engine components; do not vendor them into the MIT core.",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "headroom-cli",
|
|
177
|
+
kind: "command",
|
|
178
|
+
command: "headroom",
|
|
179
|
+
priority: 50,
|
|
180
|
+
quality: 0.82,
|
|
181
|
+
costClass: "medium",
|
|
182
|
+
latencyClass: "medium",
|
|
183
|
+
metadata: {
|
|
184
|
+
default: false,
|
|
185
|
+
external: true,
|
|
186
|
+
experimental: true,
|
|
187
|
+
reversible: true,
|
|
188
|
+
outputShaper: false,
|
|
189
|
+
upstream: "headroomlabs-ai/headroom",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
filesystem: [
|
|
194
|
+
{ id: "node-fs", kind: "builtin", priority: 100, quality: 0.95, costClass: "low", latencyClass: "fast" },
|
|
195
|
+
],
|
|
196
|
+
git: [
|
|
197
|
+
{ id: "git-cli", kind: "command", command: "git", priority: 100, quality: 0.95, costClass: "low", latencyClass: "fast" },
|
|
198
|
+
],
|
|
199
|
+
"agent.host": [
|
|
200
|
+
{ id: "pi-cli", kind: "command", command: "pi", priority: 100, quality: 0.95, costClass: "low", latencyClass: "fast" },
|
|
201
|
+
],
|
|
202
|
+
github: [
|
|
203
|
+
{ id: "gh-cli", kind: "command", command: "gh", priority: 80, quality: 0.85, costClass: "low", latencyClass: "fast" },
|
|
204
|
+
],
|
|
205
|
+
browser: [
|
|
206
|
+
{ id: "project-playwright", kind: "package", package: "playwright", priority: 90, quality: 0.9, costClass: "medium", latencyClass: "medium" },
|
|
207
|
+
{ id: "project-playwright-core", kind: "package", package: "playwright-core", priority: 70, quality: 0.75, costClass: "medium", latencyClass: "medium" },
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function readCapabilityConfig(root, file) {
|
|
214
|
+
const target = file
|
|
215
|
+
? path.resolve(file)
|
|
216
|
+
: process.env.UES_CAPABILITY_CONFIG
|
|
217
|
+
? path.resolve(process.env.UES_CAPABILITY_CONFIG)
|
|
218
|
+
: path.join(path.resolve(root), ".ues-capabilities.json")
|
|
219
|
+
if (!existsSync(target)) return null
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(await readFile(target, "utf8"))
|
|
222
|
+
return parsed && typeof parsed === "object" ? parsed : null
|
|
223
|
+
} catch {
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function mergeRegistry(base, extra) {
|
|
229
|
+
if (!extra?.capabilities || typeof extra.capabilities !== "object") return base
|
|
230
|
+
const capabilities = { ...base.capabilities }
|
|
231
|
+
for (const [name, values] of Object.entries(extra.capabilities)) {
|
|
232
|
+
const custom = Array.isArray(values) ? values : []
|
|
233
|
+
const seen = new Set(custom.map((item) => item?.id).filter(Boolean))
|
|
234
|
+
const inherited = (capabilities[name] || []).filter((item) => !seen.has(item.id))
|
|
235
|
+
capabilities[name] = [...custom, ...inherited]
|
|
236
|
+
}
|
|
237
|
+
return { ...base, capabilities }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function loadCapabilityRegistry(root = process.cwd(), options = {}) {
|
|
241
|
+
root = path.resolve(root)
|
|
242
|
+
const base = options.registry || defaultCapabilityRegistry(root)
|
|
243
|
+
if (options.registry) return base
|
|
244
|
+
return mergeRegistry(base, await readCapabilityConfig(root, options.configFile))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function probeCapabilityProvider(provider, root = process.cwd()) {
|
|
248
|
+
const normalized = normalizeProvider(provider)
|
|
249
|
+
const checkedAt = new Date().toISOString()
|
|
250
|
+
if (normalized.kind === "builtin") {
|
|
251
|
+
return { ...normalized, status: "healthy", reason: "built-in", checkedAt }
|
|
252
|
+
}
|
|
253
|
+
if (normalized.kind === "command") {
|
|
254
|
+
const available = commandExists(normalized.command)
|
|
255
|
+
return {
|
|
256
|
+
...normalized,
|
|
257
|
+
status: available ? "healthy" : "unavailable",
|
|
258
|
+
reason: available ? `command:${normalized.command}` : `missing-command:${normalized.command}`,
|
|
259
|
+
checkedAt,
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (normalized.kind === "path") {
|
|
263
|
+
const target = normalized.path ? path.resolve(root, normalized.path) : null
|
|
264
|
+
const available = Boolean(target && existsSync(target))
|
|
265
|
+
return {
|
|
266
|
+
...normalized,
|
|
267
|
+
status: available ? "healthy" : "unavailable",
|
|
268
|
+
reason: available ? `path:${normalized.path}` : `missing-path:${normalized.path || ""}`,
|
|
269
|
+
checkedAt,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (normalized.kind === "package") {
|
|
273
|
+
const declared = normalized.package ? await packageDeclared(root, normalized.package) : false
|
|
274
|
+
const installed = normalized.package
|
|
275
|
+
? existsSync(path.join(root, "node_modules", normalized.package))
|
|
276
|
+
: false
|
|
277
|
+
const available = installed || declared
|
|
278
|
+
return {
|
|
279
|
+
...normalized,
|
|
280
|
+
status: installed ? "healthy" : declared ? "degraded" : "unavailable",
|
|
281
|
+
reason: installed
|
|
282
|
+
? `installed-package:${normalized.package}`
|
|
283
|
+
: declared
|
|
284
|
+
? `declared-package:${normalized.package}`
|
|
285
|
+
: `missing-package:${normalized.package || ""}`,
|
|
286
|
+
checkedAt,
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { ...normalized, status: "unknown", reason: `unknown-kind:${normalized.kind}`, checkedAt }
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function selectCapabilityProvider(capability, providers = [], options = {}) {
|
|
293
|
+
const observations = options.observations || {}
|
|
294
|
+
const allowUnknown = options.allowUnknown === true
|
|
295
|
+
const rows = providers.map((provider, index) => {
|
|
296
|
+
const normalized = normalizeProvider(provider, index)
|
|
297
|
+
const status = provider.status || "unknown"
|
|
298
|
+
const observation = observations[normalized.id] || {}
|
|
299
|
+
const failureRate = clamp(observation.failureRate, 0, 1, 0)
|
|
300
|
+
const successRate = clamp(observation.successRate, 0, 1, 0)
|
|
301
|
+
const samples = Math.max(0, Number(observation.samples || 0))
|
|
302
|
+
const observationConfidence = samples > 0 ? Math.min(1, samples / 5) : 0
|
|
303
|
+
const successBoost = successRate * 12 * observationConfidence
|
|
304
|
+
const failurePenalty = failureRate * 35 * observationConfidence
|
|
305
|
+
const healthWeight = HEALTH_WEIGHT[status] ?? HEALTH_WEIGHT.unknown
|
|
306
|
+
const eligible = status === "healthy" || status === "degraded" || (allowUnknown && status === "unknown")
|
|
307
|
+
const score =
|
|
308
|
+
normalized.priority +
|
|
309
|
+
normalized.quality * 20 +
|
|
310
|
+
successBoost +
|
|
311
|
+
healthWeight -
|
|
312
|
+
(COST_PENALTY[normalized.costClass] ?? 5) -
|
|
313
|
+
(LATENCY_PENALTY[normalized.latencyClass] ?? 3) -
|
|
314
|
+
failurePenalty
|
|
315
|
+
return { ...normalized, status, eligible, samples, successRate, failureRate, observationConfidence: Number(observationConfidence.toFixed(3)), score: Number(score.toFixed(3)), reason: provider.reason || null }
|
|
316
|
+
})
|
|
317
|
+
const eligible = rows.filter((item) => item.eligible).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
|
|
318
|
+
return {
|
|
319
|
+
schemaVersion: 1,
|
|
320
|
+
capability,
|
|
321
|
+
selected: eligible[0] || null,
|
|
322
|
+
fallbacks: eligible.slice(1),
|
|
323
|
+
candidates: rows.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)),
|
|
324
|
+
fallbackNeeded: eligible.length === 0,
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export async function capabilityFabricStatus(root = process.cwd(), options = {}) {
|
|
329
|
+
root = path.resolve(root)
|
|
330
|
+
const registry = await loadCapabilityRegistry(root, options)
|
|
331
|
+
const persistedObservations = options.observations ? null : await readCapabilityObservations(root)
|
|
332
|
+
const capabilities = {}
|
|
333
|
+
const rows = []
|
|
334
|
+
for (const [name, providers] of Object.entries(registry.capabilities || {})) {
|
|
335
|
+
const probed = []
|
|
336
|
+
for (const provider of providers || []) probed.push(await probeCapabilityProvider(provider, root))
|
|
337
|
+
const selected = selectCapabilityProvider(name, probed, {
|
|
338
|
+
observations: options.observations?.[name] || persistedObservations?.capabilities?.[name] || {},
|
|
339
|
+
allowUnknown: options.allowUnknown,
|
|
340
|
+
})
|
|
341
|
+
capabilities[name] = selected
|
|
342
|
+
rows.push({
|
|
343
|
+
capability: name,
|
|
344
|
+
selected: selected.selected?.id || null,
|
|
345
|
+
status: selected.selected?.status || "unavailable",
|
|
346
|
+
fallbacks: selected.fallbacks.map((item) => item.id),
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
schemaVersion: 1,
|
|
351
|
+
root,
|
|
352
|
+
generatedAt: new Date().toISOString(),
|
|
353
|
+
rows,
|
|
354
|
+
capabilities,
|
|
355
|
+
healthyCapabilities: rows.filter((item) => item.selected).length,
|
|
356
|
+
totalCapabilities: rows.length,
|
|
357
|
+
observationState: persistedObservations ? { updatedAt: persistedObservations.updatedAt, capabilities: Object.keys(persistedObservations.capabilities || {}).length } : null,
|
|
358
|
+
}
|
|
359
|
+
}
|
|
@@ -107,3 +107,12 @@ export function modelCandidatesFromPolicy(policy = {}) {
|
|
|
107
107
|
export function roleCapabilityRequirements(role) {
|
|
108
108
|
return { ...(ROLE_REQUIREMENTS[role] || {}) }
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
capabilityFabricStatus,
|
|
114
|
+
defaultCapabilityRegistry,
|
|
115
|
+
loadCapabilityRegistry,
|
|
116
|
+
probeCapabilityProvider,
|
|
117
|
+
selectCapabilityProvider,
|
|
118
|
+
} from "./capability-fabric.mjs"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
|
|
3
|
+
const DEFAULT_HASH_CHARS = 8
|
|
4
|
+
|
|
5
|
+
function normalizeNewlines(value) {
|
|
6
|
+
return String(value ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function hashText(value, chars = DEFAULT_HASH_CHARS) {
|
|
10
|
+
return createHash("sha256").update(String(value ?? "")).digest("hex").slice(0, Math.max(6, Math.min(16, Number(chars || DEFAULT_HASH_CHARS))))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function lineAnchor(lineNumber, lineText, options = {}) {
|
|
14
|
+
const line = Math.max(1, Math.trunc(Number(lineNumber || 1)))
|
|
15
|
+
return `L${line}:${hashText(lineText, options.hashChars)}`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseLineAnchor(anchor) {
|
|
19
|
+
const match = String(anchor || "").trim().match(/^L([1-9]\d*):([a-f0-9]{6,16})$/i)
|
|
20
|
+
if (!match) return null
|
|
21
|
+
return { line: Number(match[1]), hash: match[2].toLowerCase() }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function anchoredLines(source, options = {}) {
|
|
25
|
+
const text = normalizeNewlines(source)
|
|
26
|
+
const lines = text.split("\n")
|
|
27
|
+
const startLine = Math.max(1, Math.trunc(Number(options.startLine || 1)))
|
|
28
|
+
const requestedEnd = options.endLine == null ? lines.length : Math.trunc(Number(options.endLine))
|
|
29
|
+
const endLine = Math.max(startLine, Math.min(lines.length, requestedEnd || lines.length))
|
|
30
|
+
const rows = []
|
|
31
|
+
for (let line = startLine; line <= endLine; line += 1) {
|
|
32
|
+
const value = lines[line - 1] ?? ""
|
|
33
|
+
rows.push({ line, anchor: lineAnchor(line, value, options), text: value })
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
schemaVersion: 1,
|
|
37
|
+
startLine,
|
|
38
|
+
endLine,
|
|
39
|
+
lineCount: lines.length,
|
|
40
|
+
sourceHash: hashText(text, 16),
|
|
41
|
+
rows,
|
|
42
|
+
text: rows.map((row) => `${row.anchor}|${row.text}`).join("\n"),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function validateAnchor(lines, rawAnchor, label, options = {}) {
|
|
47
|
+
const parsed = parseLineAnchor(rawAnchor)
|
|
48
|
+
if (!parsed) throw new Error(`${label} must use L<line>:<hash> format`)
|
|
49
|
+
if (parsed.line > lines.length) throw new Error(`${label} points beyond end of file`)
|
|
50
|
+
const actual = lineAnchor(parsed.line, lines[parsed.line - 1] ?? "", options)
|
|
51
|
+
if (actual.toLowerCase() !== String(rawAnchor).toLowerCase()) {
|
|
52
|
+
const error = new Error(`${label} is stale or ambiguous; re-read anchored source before editing`)
|
|
53
|
+
error.code = "UES_STALE_ANCHOR"
|
|
54
|
+
error.expected = actual
|
|
55
|
+
error.received = String(rawAnchor)
|
|
56
|
+
throw error
|
|
57
|
+
}
|
|
58
|
+
return parsed
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function applyAnchoredEdits(source, edits = [], options = {}) {
|
|
62
|
+
const text = normalizeNewlines(source)
|
|
63
|
+
const lines = text.split("\n")
|
|
64
|
+
if (!Array.isArray(edits) || edits.length === 0) throw new Error("at least one anchored edit is required")
|
|
65
|
+
const maxEdits = Math.max(1, Math.min(200, Number(options.maxEdits || 50)))
|
|
66
|
+
if (edits.length > maxEdits) throw new Error(`too many anchored edits; maximum is ${maxEdits}`)
|
|
67
|
+
|
|
68
|
+
const prepared = edits.map((edit, index) => {
|
|
69
|
+
const start = validateAnchor(lines, edit?.anchor || edit?.startAnchor, `edit[${index}].anchor`, options)
|
|
70
|
+
const end = edit?.endAnchor
|
|
71
|
+
? validateAnchor(lines, edit.endAnchor, `edit[${index}].endAnchor`, options)
|
|
72
|
+
: start
|
|
73
|
+
if (end.line < start.line) throw new Error(`edit[${index}] endAnchor precedes anchor`)
|
|
74
|
+
return {
|
|
75
|
+
index,
|
|
76
|
+
startLine: start.line,
|
|
77
|
+
endLine: end.line,
|
|
78
|
+
replacement: normalizeNewlines(edit?.replacement ?? ""),
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
prepared.sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine)
|
|
83
|
+
for (let index = 1; index < prepared.length; index += 1) {
|
|
84
|
+
if (prepared[index].startLine <= prepared[index - 1].endLine) {
|
|
85
|
+
throw new Error(`anchored edits overlap at edit[${prepared[index].index}]`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const next = [...lines]
|
|
90
|
+
for (const edit of [...prepared].sort((a, b) => b.startLine - a.startLine)) {
|
|
91
|
+
const replacementLines = edit.replacement.split("\n")
|
|
92
|
+
next.splice(edit.startLine - 1, edit.endLine - edit.startLine + 1, ...replacementLines)
|
|
93
|
+
}
|
|
94
|
+
const output = next.join("\n")
|
|
95
|
+
return {
|
|
96
|
+
schemaVersion: 1,
|
|
97
|
+
sourceHash: hashText(text, 16),
|
|
98
|
+
outputHash: hashText(output, 16),
|
|
99
|
+
applied: prepared.length,
|
|
100
|
+
text: output,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { lstat, readFile, realpath, rename, rm, writeFile } from "node:fs/promises"
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { querySemanticIndex } from "../semantic-index.mjs"
|
|
5
|
+
import { anchoredLines, applyAnchoredEdits } from "./edit-anchor.mjs"
|
|
6
|
+
import { diagnoseCode, lspProviderStatus } from "./lsp-provider.mjs"
|
|
7
|
+
|
|
8
|
+
const AST_COMMANDS = ["ast-grep", "sg"]
|
|
9
|
+
const commandCache = new Map()
|
|
10
|
+
const COMMAND_TTL_MS = 60_000
|
|
11
|
+
|
|
12
|
+
function commandExists(command) {
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
const cached = commandCache.get(command)
|
|
15
|
+
if (cached && now - cached.at < COMMAND_TTL_MS) return cached.available
|
|
16
|
+
const finder = process.platform === "win32" ? "where" : "which"
|
|
17
|
+
const available = spawnSync(finder, [command], { stdio: "ignore", windowsHide: true }).status === 0
|
|
18
|
+
commandCache.set(command, { available, at: now })
|
|
19
|
+
return available
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function inside(root, target) { return target === root || target.startsWith(root + path.sep) }
|
|
23
|
+
|
|
24
|
+
async function safeFile(root, relative) {
|
|
25
|
+
const base = await realpath(path.resolve(root)).catch(() => path.resolve(root))
|
|
26
|
+
const requested = path.resolve(base, String(relative || ""))
|
|
27
|
+
if (!inside(base, requested)) throw new Error("code path escapes workspace root")
|
|
28
|
+
const info = await lstat(requested)
|
|
29
|
+
if (!info.isFile()) throw new Error("code path is not a file")
|
|
30
|
+
const actual = await realpath(requested)
|
|
31
|
+
if (!inside(base, actual)) throw new Error("code symlink escapes workspace root")
|
|
32
|
+
return { base, file: actual, info }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function probeCodeIntelligence(file = "") {
|
|
36
|
+
const astProvider = AST_COMMANDS.find(commandExists) || null
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: 1,
|
|
39
|
+
anchoredEditing: true,
|
|
40
|
+
semanticIndex: true,
|
|
41
|
+
astProvider,
|
|
42
|
+
lsp: lspProviderStatus(file),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function readAnchoredCode(root, relative, options = {}) {
|
|
47
|
+
const target = await safeFile(root, relative)
|
|
48
|
+
const maxBytes = Math.max(8 * 1024, Math.min(4 * 1024 * 1024, Number(options.maxBytes || 512 * 1024)))
|
|
49
|
+
if (target.info.size > maxBytes) throw new Error("code file exceeds anchored-read limit (" + target.info.size + " > " + maxBytes + " bytes)")
|
|
50
|
+
const source = await readFile(target.file, "utf8")
|
|
51
|
+
return { file: path.relative(target.base, target.file).replaceAll("\\", "/"), ...anchoredLines(source, options) }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function applyAnchoredFileEdits(root, relative, edits, options = {}) {
|
|
55
|
+
const target = await safeFile(root, relative)
|
|
56
|
+
const maxBytes = Math.max(8 * 1024, Math.min(4 * 1024 * 1024, Number(options.maxBytes || 512 * 1024)))
|
|
57
|
+
if (target.info.size > maxBytes) throw new Error("code file exceeds anchored-edit limit (" + target.info.size + " > " + maxBytes + " bytes)")
|
|
58
|
+
const source = await readFile(target.file, "utf8")
|
|
59
|
+
const result = applyAnchoredEdits(source, edits, options)
|
|
60
|
+
const relativeFile = path.relative(target.base, target.file).replaceAll("\\", "/")
|
|
61
|
+
if (options.dryRun === true) return { ...result, file: relativeFile, dryRun: true }
|
|
62
|
+
const temp = target.file + "." + process.pid + "." + Date.now() + ".ues.tmp"
|
|
63
|
+
await writeFile(temp, result.text, "utf8")
|
|
64
|
+
try { await rename(temp, target.file) } catch (error) { await rm(temp, { force: true }).catch(() => {}); throw error }
|
|
65
|
+
return { ...result, file: relativeFile, dryRun: false }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function runAstGrep(root, pattern, language, limit) {
|
|
69
|
+
const command = AST_COMMANDS.find(commandExists) || null
|
|
70
|
+
if (!command || !String(pattern || "").trim()) return { provider: null, results: [], error: null }
|
|
71
|
+
const args = ["run", "--pattern", String(pattern), "--json=stream"]
|
|
72
|
+
if (language) args.push("--lang", String(language))
|
|
73
|
+
args.push(".")
|
|
74
|
+
const result = spawnSync(command, args, { cwd: path.resolve(root), encoding: "utf8", timeout: 20_000, maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
|
75
|
+
if (result.status !== 0) return { provider: command, results: [], error: String(result.stderr || ("ast-grep exit " + result.status)).trim().slice(0, 1000) }
|
|
76
|
+
const rows = String(result.stdout || "").split(/\r?\n/).filter(Boolean).slice(0, limit).map((line) => {
|
|
77
|
+
try { return JSON.parse(line) } catch { return { raw: line } }
|
|
78
|
+
})
|
|
79
|
+
return { provider: command, results: rows, error: null }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function searchCodeIntelligence(root, query, options = {}) {
|
|
83
|
+
const maxResults = Math.max(1, Math.min(50, Number(options.maxResults || 12)))
|
|
84
|
+
const semantic = await querySemanticIndex(root, query, { maxFiles: options.maxFiles || 6000, limit: maxResults })
|
|
85
|
+
const structural = options.structuralPattern ? runAstGrep(root, options.structuralPattern, options.language, maxResults) : { provider: null, results: [], error: null }
|
|
86
|
+
return {
|
|
87
|
+
schemaVersion: 1,
|
|
88
|
+
query: String(query || ""),
|
|
89
|
+
providers: probeCodeIntelligence(options.file || ""),
|
|
90
|
+
semantic: { ...semantic, results: (semantic.results || []).slice(0, maxResults) },
|
|
91
|
+
structural,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { diagnoseCode, lspProviderStatus } from "./lsp-provider.mjs"
|