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,189 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process"
|
|
2
|
+
import { lstat, readFile, realpath } from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { pathToFileURL } from "node:url"
|
|
5
|
+
|
|
6
|
+
const PROVIDERS = [
|
|
7
|
+
{ id: "typescript-language-server", extensions: new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]), commands: [["typescript-language-server", ["--stdio"]]], languageId: (ext) => [".ts", ".tsx"].includes(ext) ? "typescript" : "javascript" },
|
|
8
|
+
{ id: "pyright-langserver", extensions: new Set([".py"]), commands: [["pyright-langserver", ["--stdio"]], ["pylsp", []]], languageId: () => "python" },
|
|
9
|
+
{ id: "gopls", extensions: new Set([".go"]), commands: [["gopls", []]], languageId: () => "go" },
|
|
10
|
+
{ id: "rust-analyzer", extensions: new Set([".rs"]), commands: [["rust-analyzer", []]], languageId: () => "rust" },
|
|
11
|
+
{ id: "clangd", extensions: new Set([".c", ".cc", ".cpp", ".cxx", ".h", ".hpp"]), commands: [["clangd", []]], languageId: (ext) => [".c", ".h"].includes(ext) ? "c" : "cpp" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const commandCache = new Map()
|
|
15
|
+
const COMMAND_TTL_MS = 60_000
|
|
16
|
+
|
|
17
|
+
function commandExists(command) {
|
|
18
|
+
const now = Date.now()
|
|
19
|
+
const cached = commandCache.get(command)
|
|
20
|
+
if (cached && now - cached.at < COMMAND_TTL_MS) return cached.available
|
|
21
|
+
const finder = process.platform === "win32" ? "where" : "which"
|
|
22
|
+
const available = spawnSync(finder, [command], { stdio: "ignore", windowsHide: true }).status === 0
|
|
23
|
+
commandCache.set(command, { available, at: now })
|
|
24
|
+
return available
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function inside(root, target) {
|
|
28
|
+
return target === root || target.startsWith(root + path.sep)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function safeFile(root, relative) {
|
|
32
|
+
const base = await realpath(path.resolve(root)).catch(() => path.resolve(root))
|
|
33
|
+
const requested = path.resolve(base, String(relative || ""))
|
|
34
|
+
if (!inside(base, requested)) throw new Error("diagnostic path escapes workspace root")
|
|
35
|
+
const info = await lstat(requested)
|
|
36
|
+
if (!info.isFile()) throw new Error("diagnostic path is not a file")
|
|
37
|
+
const actual = await realpath(requested)
|
|
38
|
+
if (!inside(base, actual)) throw new Error("diagnostic symlink escapes workspace root")
|
|
39
|
+
return { base, file: actual, info }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function providerFor(file) {
|
|
43
|
+
const ext = path.extname(file).toLowerCase()
|
|
44
|
+
for (const provider of PROVIDERS) {
|
|
45
|
+
if (!provider.extensions.has(ext)) continue
|
|
46
|
+
for (const [command, args] of provider.commands) {
|
|
47
|
+
if (commandExists(command)) return { ...provider, command, args, languageId: provider.languageId(ext) }
|
|
48
|
+
}
|
|
49
|
+
return { ...provider, command: null, args: [], languageId: provider.languageId(ext) }
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function lspProviderStatus(file = "") {
|
|
55
|
+
const ext = path.extname(String(file || "")).toLowerCase()
|
|
56
|
+
const providers = PROVIDERS
|
|
57
|
+
.filter((provider) => !ext || provider.extensions.has(ext))
|
|
58
|
+
.map((provider) => {
|
|
59
|
+
const selected = provider.commands.find(([command]) => commandExists(command)) || null
|
|
60
|
+
return { id: provider.id, available: Boolean(selected), command: selected?.[0] || null, extensions: [...provider.extensions] }
|
|
61
|
+
})
|
|
62
|
+
return { schemaVersion: 1, extension: ext, available: providers.some((row) => row.available), providers }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function encodeMessage(payload) {
|
|
66
|
+
const body = Buffer.from(JSON.stringify(payload), "utf8")
|
|
67
|
+
return Buffer.concat([Buffer.from("Content-Length: " + body.length + "\r\n\r\n", "ascii"), body])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sanitizeDiagnostic(value = {}) {
|
|
71
|
+
return {
|
|
72
|
+
range: value.range || null,
|
|
73
|
+
severity: Number.isFinite(Number(value.severity)) ? Number(value.severity) : null,
|
|
74
|
+
code: value.code == null ? null : String(value.code),
|
|
75
|
+
source: value.source == null ? null : String(value.source),
|
|
76
|
+
message: String(value.message || "").slice(0, 4000),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function diagnoseCode(root, relative, options = {}) {
|
|
81
|
+
const target = await safeFile(root, relative)
|
|
82
|
+
const provider = providerFor(target.file)
|
|
83
|
+
const relativeFile = path.relative(target.base, target.file).replaceAll("\\", "/")
|
|
84
|
+
if (!provider?.command) {
|
|
85
|
+
return { schemaVersion: 1, file: relativeFile, available: false, provider: provider?.id || null, diagnostics: [], reason: provider ? "lsp-command-unavailable" : "unsupported-extension" }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const maxBytes = Math.max(8 * 1024, Math.min(4 * 1024 * 1024, Number(options.maxBytes || 1024 * 1024)))
|
|
89
|
+
if (target.info.size > maxBytes) throw new Error("diagnostic file exceeds limit (" + target.info.size + " > " + maxBytes + " bytes)")
|
|
90
|
+
const source = await readFile(target.file, "utf8")
|
|
91
|
+
const uri = pathToFileURL(target.file).href
|
|
92
|
+
const rootUri = pathToFileURL(target.base + path.sep).href
|
|
93
|
+
const timeoutMs = Math.max(500, Math.min(10_000, Number(options.timeoutMs || 3000)))
|
|
94
|
+
const maxDiagnostics = Math.max(1, Math.min(500, Number(options.maxDiagnostics || 100)))
|
|
95
|
+
|
|
96
|
+
return await new Promise((resolve) => {
|
|
97
|
+
const child = spawn(provider.command, provider.args, { cwd: target.base, stdio: ["pipe", "pipe", "pipe"], windowsHide: true, shell: false })
|
|
98
|
+
let buffer = Buffer.alloc(0)
|
|
99
|
+
let stderr = ""
|
|
100
|
+
let settled = false
|
|
101
|
+
let initialized = false
|
|
102
|
+
let diagnostics = []
|
|
103
|
+
let timer = null
|
|
104
|
+
|
|
105
|
+
const finish = (reason, error = null) => {
|
|
106
|
+
if (settled) return
|
|
107
|
+
settled = true
|
|
108
|
+
if (timer) clearTimeout(timer)
|
|
109
|
+
try {
|
|
110
|
+
if (initialized && child.stdin.writable) {
|
|
111
|
+
child.stdin.write(encodeMessage({ jsonrpc: "2.0", id: 2, method: "shutdown", params: null }))
|
|
112
|
+
child.stdin.write(encodeMessage({ jsonrpc: "2.0", method: "exit", params: null }))
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
setTimeout(() => { try { child.kill() } catch {} }, 50).unref?.()
|
|
116
|
+
resolve({
|
|
117
|
+
schemaVersion: 1,
|
|
118
|
+
file: relativeFile,
|
|
119
|
+
available: true,
|
|
120
|
+
provider: provider.id,
|
|
121
|
+
command: provider.command,
|
|
122
|
+
diagnostics: diagnostics.slice(0, maxDiagnostics),
|
|
123
|
+
reason,
|
|
124
|
+
stderr: stderr.trim().slice(0, 2000) || null,
|
|
125
|
+
error: error ? String(error instanceof Error ? error.message : error).slice(0, 1000) : null,
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const handle = (message) => {
|
|
130
|
+
if (message?.id === 1 && message?.result) {
|
|
131
|
+
initialized = true
|
|
132
|
+
try {
|
|
133
|
+
child.stdin.write(encodeMessage({ jsonrpc: "2.0", method: "initialized", params: {} }))
|
|
134
|
+
child.stdin.write(encodeMessage({
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
method: "textDocument/didOpen",
|
|
137
|
+
params: { textDocument: { uri, languageId: provider.languageId, version: 1, text: source } },
|
|
138
|
+
}))
|
|
139
|
+
} catch (error) {
|
|
140
|
+
finish("write-failed", error)
|
|
141
|
+
}
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if (message?.method === "textDocument/publishDiagnostics" && message?.params?.uri === uri) {
|
|
145
|
+
diagnostics = Array.isArray(message.params.diagnostics) ? message.params.diagnostics.map(sanitizeDiagnostic).slice(0, maxDiagnostics) : []
|
|
146
|
+
finish("publishDiagnostics")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const parse = () => {
|
|
151
|
+
while (!settled) {
|
|
152
|
+
const headerEnd = buffer.indexOf("\r\n\r\n")
|
|
153
|
+
if (headerEnd < 0) return
|
|
154
|
+
const header = buffer.subarray(0, headerEnd).toString("ascii")
|
|
155
|
+
const match = header.match(/Content-Length:\s*(\d+)/i)
|
|
156
|
+
if (!match) { buffer = buffer.subarray(headerEnd + 4); continue }
|
|
157
|
+
const length = Number(match[1])
|
|
158
|
+
const bodyStart = headerEnd + 4
|
|
159
|
+
if (buffer.length < bodyStart + length) return
|
|
160
|
+
const body = buffer.subarray(bodyStart, bodyStart + length).toString("utf8")
|
|
161
|
+
buffer = buffer.subarray(bodyStart + length)
|
|
162
|
+
try { handle(JSON.parse(body)) } catch {}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
child.stdout.on("data", (chunk) => { buffer = Buffer.concat([buffer, Buffer.from(chunk)]); parse() })
|
|
167
|
+
child.stderr.on("data", (chunk) => { if (stderr.length < 64 * 1024) stderr += chunk.toString() })
|
|
168
|
+
child.on("error", (error) => finish("spawn-error", error))
|
|
169
|
+
child.on("close", (code) => { if (!settled) finish("server-exit:" + (code ?? "unknown")) })
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
child.stdin.write(encodeMessage({
|
|
173
|
+
jsonrpc: "2.0",
|
|
174
|
+
id: 1,
|
|
175
|
+
method: "initialize",
|
|
176
|
+
params: {
|
|
177
|
+
processId: process.pid,
|
|
178
|
+
rootUri,
|
|
179
|
+
capabilities: { textDocument: { publishDiagnostics: { relatedInformation: true } } },
|
|
180
|
+
workspaceFolders: [{ uri: rootUri, name: path.basename(target.base) || "workspace" }],
|
|
181
|
+
},
|
|
182
|
+
}))
|
|
183
|
+
} catch (error) {
|
|
184
|
+
finish("initialize-write-failed", error)
|
|
185
|
+
}
|
|
186
|
+
timer = setTimeout(() => finish("timeout"), timeoutMs)
|
|
187
|
+
timer.unref?.()
|
|
188
|
+
})
|
|
189
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function text(value) {
|
|
2
|
+
return String(value ?? "").trim()
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function reportSections(run) {
|
|
6
|
+
const sections = run?.report?.sections
|
|
7
|
+
return sections && typeof sections === "object" ? sections : {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function passRun(run) {
|
|
11
|
+
return Boolean(run && Number(run.exitCode) === 0 && run.verdict === "PASS")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function benignEmptySection(value) {
|
|
15
|
+
const normalized = text(value)
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[`*_#]/g, "")
|
|
18
|
+
.replace(/[.!:;]+$/g, "")
|
|
19
|
+
.trim()
|
|
20
|
+
return !normalized || /^(?:none(?: observed| found| remaining)?|n\/a|na|nil|0|no failures?(?: observed| found| remaining)?|no unresolved gaps?(?: observed| found| remaining)?|no gaps?(?: observed| found| remaining)?|not applicable)$/.test(normalized)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function receiptPassed(row = {}) {
|
|
24
|
+
const receipt = row?.receipt || row
|
|
25
|
+
return Boolean(receipt?.passed === true && Number(receipt?.exitCode) === 0)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function auditCompletion(input = {}) {
|
|
29
|
+
const failures = []
|
|
30
|
+
const warnings = []
|
|
31
|
+
const verification = input.verification || null
|
|
32
|
+
const integration = input.integration || null
|
|
33
|
+
const visual = input.visual || null
|
|
34
|
+
const requireIntegration = input.requireIntegration === true
|
|
35
|
+
const requireVisual = input.requireVisual === true
|
|
36
|
+
const receipts = Array.isArray(input.behavioralReceipts) ? input.behavioralReceipts : []
|
|
37
|
+
|
|
38
|
+
if (!passRun(verification)) failures.push("primary-verification-not-pass")
|
|
39
|
+
if (requireIntegration && !passRun(integration)) failures.push("integration-verification-not-pass")
|
|
40
|
+
if (requireVisual && !passRun(visual)) failures.push("visual-verification-not-pass")
|
|
41
|
+
|
|
42
|
+
const sections = reportSections(verification)
|
|
43
|
+
for (const key of ["checks-run", "acceptance-criteria-proven", "completion-evidence"]) {
|
|
44
|
+
if (!text(sections[key])) failures.push(`missing-report-section:${key}`)
|
|
45
|
+
}
|
|
46
|
+
if (Object.hasOwn(sections, "failures") && !benignEmptySection(sections.failures)) failures.push("verification-reports-failures")
|
|
47
|
+
if (Object.hasOwn(sections, "unresolved-gaps") && !benignEmptySection(sections["unresolved-gaps"])) failures.push("verification-reports-unresolved-gaps")
|
|
48
|
+
if (Object.hasOwn(sections, "checks-not-run") && !benignEmptySection(sections["checks-not-run"])) warnings.push("verification-reports-checks-not-run")
|
|
49
|
+
|
|
50
|
+
const snapshot = input.workspaceSnapshot || null
|
|
51
|
+
if (snapshot) {
|
|
52
|
+
if (snapshot.cacheable === true && !text(snapshot.fingerprint)) failures.push("workspace-fingerprint-missing")
|
|
53
|
+
if (Array.isArray(snapshot.changedFiles) && snapshot.changedFiles.length === 0) warnings.push("workspace-has-no-detected-changes")
|
|
54
|
+
} else {
|
|
55
|
+
warnings.push("workspace-snapshot-unavailable")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const passingReceipts = receipts.filter(receiptPassed)
|
|
59
|
+
if (input.requireBehavioralReceipt === true && passingReceipts.length === 0) failures.push("fresh-behavioral-receipt-missing")
|
|
60
|
+
else if (passingReceipts.length === 0) warnings.push("no-fresh-behavioral-receipt-attached")
|
|
61
|
+
|
|
62
|
+
const verifierOutput = text(verification?.output)
|
|
63
|
+
if (verification?.report?.valid === false) failures.push("verification-report-invalid")
|
|
64
|
+
if (verification?.report?.verdict && verification.report.verdict !== "PASS") failures.push("verification-report-verdict-mismatch")
|
|
65
|
+
if (/UES_VERDICT:\s*FAIL/i.test(verifierOutput)) failures.push("verifier-output-contains-fail-verdict")
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
schemaVersion: 1,
|
|
69
|
+
passed: failures.length === 0,
|
|
70
|
+
failures: [...new Set(failures)],
|
|
71
|
+
warnings: [...new Set(warnings)],
|
|
72
|
+
evidence: {
|
|
73
|
+
primary: passRun(verification),
|
|
74
|
+
integration: requireIntegration ? passRun(integration) : null,
|
|
75
|
+
visual: requireVisual ? passRun(visual) : null,
|
|
76
|
+
reportSections: Object.keys(sections).sort(),
|
|
77
|
+
passingReceiptCount: passingReceipts.length,
|
|
78
|
+
workspaceFingerprint: snapshot?.fingerprint || null,
|
|
79
|
+
changedFiles: Array.isArray(snapshot?.changedFiles) ? snapshot.changedFiles.slice(0, 200) : [],
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -4,6 +4,8 @@ import { putEvidence } from "./evidence-store.mjs"
|
|
|
4
4
|
import { inferTaskCapabilities } from "./capability-registry.mjs"
|
|
5
5
|
import { buildPromptEnvelope, comparePromptEnvelopes } from "./prompt-cache.mjs"
|
|
6
6
|
import { measureContextQuality } from "./context-quality.mjs"
|
|
7
|
+
import { retrieveMemories } from "./memory-engine.mjs"
|
|
8
|
+
import { capabilityFabricStatus } from "./capability-fabric.mjs"
|
|
7
9
|
|
|
8
10
|
function taskText(task = {}) {
|
|
9
11
|
return [
|
|
@@ -14,6 +16,20 @@ function taskText(task = {}) {
|
|
|
14
16
|
].filter(Boolean).join(" ")
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
function taskFilesForMemory(task = {}) {
|
|
20
|
+
const files = task.files
|
|
21
|
+
const values = []
|
|
22
|
+
if (Array.isArray(files)) values.push(...files)
|
|
23
|
+
else if (files && typeof files === "object") {
|
|
24
|
+
for (const value of Object.values(files)) {
|
|
25
|
+
if (Array.isArray(value)) values.push(...value)
|
|
26
|
+
else if (typeof value === "string") values.push(value)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(task.requiredFiles)) values.push(...task.requiredFiles)
|
|
30
|
+
return [...new Set(values.filter(Boolean))]
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
function rankExcerpt(item = {}, task = {}) {
|
|
18
34
|
const text = taskText(task).toLowerCase()
|
|
19
35
|
const pathValue = String(item.path || "").toLowerCase()
|
|
@@ -91,6 +107,8 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
91
107
|
strategy: options.strategy || policy?.profile?.contextStrategy || "incremental-semantic+git",
|
|
92
108
|
semanticMaxFiles: options.semanticMaxFiles,
|
|
93
109
|
maxFiles: options.maxFiles,
|
|
110
|
+
changedFiles: options.changedFiles,
|
|
111
|
+
workspaceFingerprint: options.workspaceFingerprint,
|
|
94
112
|
})
|
|
95
113
|
|
|
96
114
|
const externalized = await externalizeContextExcerpts(root, manifest, {
|
|
@@ -99,6 +117,24 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
99
117
|
inlineChars: options.inlineChars,
|
|
100
118
|
})
|
|
101
119
|
|
|
120
|
+
const memoryRetrieval = await retrieveMemories(root, taskText(task), {
|
|
121
|
+
files: taskFilesForMemory(task),
|
|
122
|
+
limit: options.memoryLimit ?? 6,
|
|
123
|
+
taskClass: options.taskClass || task.type || policy.executionProfile || policy.profile?.name || null,
|
|
124
|
+
touch: options.touchMemories !== false,
|
|
125
|
+
}).catch(() => ({ schemaVersion: 1, query: taskText(task), eligible: 0, results: [] }))
|
|
126
|
+
const memories = memoryRetrieval.results || []
|
|
127
|
+
const fabric = await capabilityFabricStatus(root).catch(() => null)
|
|
128
|
+
const providerHints = (fabric?.rows || [])
|
|
129
|
+
.filter((row) => row.selected)
|
|
130
|
+
.slice(0, 12)
|
|
131
|
+
.map((row) => ({
|
|
132
|
+
capability: row.capability,
|
|
133
|
+
selected: row.selected,
|
|
134
|
+
status: row.status,
|
|
135
|
+
fallbacks: (row.fallbacks || []).slice(0, 3),
|
|
136
|
+
}))
|
|
137
|
+
|
|
102
138
|
const promptEnvelope = buildPromptEnvelope({
|
|
103
139
|
invariants: options.invariants || "evidence-first; scoped edits; fresh verification; no unsupported completion claims",
|
|
104
140
|
role: options.role || "executor",
|
|
@@ -114,6 +150,23 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
114
150
|
...(externalized.manifest?.rankedReferences || []).slice(0, 12).map((item) => item.path),
|
|
115
151
|
...(options.evidence || []),
|
|
116
152
|
],
|
|
153
|
+
memories: memories.map((item) => ({
|
|
154
|
+
id: item.id,
|
|
155
|
+
type: item.type,
|
|
156
|
+
scope: item.scope,
|
|
157
|
+
content: item.content,
|
|
158
|
+
confidence: item.confidence,
|
|
159
|
+
files: item.files,
|
|
160
|
+
retrieval: item.retrieval,
|
|
161
|
+
})),
|
|
162
|
+
contextHints: {
|
|
163
|
+
hierarchy: (externalized.manifest?.hierarchy?.scopes || []).slice(0, 6).map((item) => ({
|
|
164
|
+
path: item.path,
|
|
165
|
+
score: item.score,
|
|
166
|
+
l0: item.l0,
|
|
167
|
+
})),
|
|
168
|
+
providers: providerHints,
|
|
169
|
+
},
|
|
117
170
|
recentFailure: options.recentFailure || null,
|
|
118
171
|
nextAction: options.nextAction || null,
|
|
119
172
|
recentMessages: options.recentMessages || [],
|
|
@@ -132,6 +185,17 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
132
185
|
evidenceBudget,
|
|
133
186
|
contextManifest: externalized.manifest,
|
|
134
187
|
contextQuality,
|
|
188
|
+
memories,
|
|
189
|
+
memoryRetrieval: {
|
|
190
|
+
schemaVersion: memoryRetrieval.schemaVersion || 1,
|
|
191
|
+
eligible: memoryRetrieval.eligible || 0,
|
|
192
|
+
returned: memories.length,
|
|
193
|
+
},
|
|
194
|
+
capabilityFabric: {
|
|
195
|
+
healthyCapabilities: fabric?.healthyCapabilities || 0,
|
|
196
|
+
totalCapabilities: fabric?.totalCapabilities || 0,
|
|
197
|
+
providers: providerHints,
|
|
198
|
+
},
|
|
135
199
|
evidenceStore: {
|
|
136
200
|
refs: externalized.externalized.length,
|
|
137
201
|
externalizedBytes: externalized.externalizedBytes,
|
|
@@ -147,4 +211,4 @@ export async function buildAdaptiveTaskContext(root, task, options = {}) {
|
|
|
147
211
|
...(cache || {}),
|
|
148
212
|
},
|
|
149
213
|
}
|
|
150
|
-
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
function normalizePath(value) {
|
|
2
|
+
return String(value || "").replaceAll("\\", "/")
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function normalizeSeeds(nodes, semanticResults = [], declared = [], changed = []) {
|
|
6
|
+
const raw = new Map(nodes.map((node) => [node, 0]))
|
|
7
|
+
const semanticMax = Math.max(1, ...semanticResults.map((item) => Number(item.score || 0)))
|
|
8
|
+
for (const item of semanticResults) {
|
|
9
|
+
const file = normalizePath(item.path)
|
|
10
|
+
if (!raw.has(file)) continue
|
|
11
|
+
raw.set(file, raw.get(file) + Number(item.score || 0) / semanticMax)
|
|
12
|
+
}
|
|
13
|
+
for (const file of declared.map(normalizePath)) {
|
|
14
|
+
if (raw.has(file)) raw.set(file, raw.get(file) + 1.25)
|
|
15
|
+
}
|
|
16
|
+
for (const file of changed.map(normalizePath)) {
|
|
17
|
+
if (raw.has(file)) raw.set(file, raw.get(file) + 0.75)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let total = [...raw.values()].reduce((sum, value) => sum + value, 0)
|
|
21
|
+
if (total <= 0) {
|
|
22
|
+
const uniform = nodes.length ? 1 / nodes.length : 0
|
|
23
|
+
return new Map(nodes.map((node) => [node, uniform]))
|
|
24
|
+
}
|
|
25
|
+
return new Map([...raw.entries()].map(([key, value]) => [key, value / total]))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function rankContextGraph(graph = {}, options = {}) {
|
|
29
|
+
const nodes = [...new Set((graph.nodes || []).map((node) => normalizePath(node.path)).filter(Boolean))]
|
|
30
|
+
if (!nodes.length) return []
|
|
31
|
+
|
|
32
|
+
const nodeSet = new Set(nodes)
|
|
33
|
+
const neighbors = new Map(nodes.map((node) => [node, new Map()]))
|
|
34
|
+
const addEdge = (from, to, weight) => {
|
|
35
|
+
if (!nodeSet.has(from) || !nodeSet.has(to) || from === to) return
|
|
36
|
+
const row = neighbors.get(from)
|
|
37
|
+
row.set(to, Number(row.get(to) || 0) + weight)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const edge of graph.edges || []) {
|
|
41
|
+
const from = normalizePath(edge.from)
|
|
42
|
+
const to = normalizePath(edge.to)
|
|
43
|
+
// Imports carry relevance in both directions: the dependency is relevant to
|
|
44
|
+
// its caller, while callers are useful when a dependency itself is seeded.
|
|
45
|
+
addEdge(from, to, 1)
|
|
46
|
+
addEdge(to, from, 0.55)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const seeds = normalizeSeeds(
|
|
50
|
+
nodes,
|
|
51
|
+
options.semanticResults || [],
|
|
52
|
+
options.declared || [],
|
|
53
|
+
options.changed || [],
|
|
54
|
+
)
|
|
55
|
+
const damping = Math.max(0.5, Math.min(0.95, Number(options.damping || 0.85)))
|
|
56
|
+
const iterations = Math.max(4, Math.min(40, Number(options.iterations || 14)))
|
|
57
|
+
let scores = new Map(seeds)
|
|
58
|
+
|
|
59
|
+
for (let iteration = 0; iteration < iterations; iteration += 1) {
|
|
60
|
+
const next = new Map(nodes.map((node) => [node, (1 - damping) * Number(seeds.get(node) || 0)]))
|
|
61
|
+
let dangling = 0
|
|
62
|
+
|
|
63
|
+
for (const node of nodes) {
|
|
64
|
+
const score = Number(scores.get(node) || 0)
|
|
65
|
+
const row = neighbors.get(node)
|
|
66
|
+
const totalWeight = [...row.values()].reduce((sum, weight) => sum + weight, 0)
|
|
67
|
+
if (totalWeight <= 0) {
|
|
68
|
+
dangling += score
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
for (const [target, weight] of row) {
|
|
72
|
+
next.set(target, Number(next.get(target) || 0) + damping * score * (weight / totalWeight))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (dangling > 0) {
|
|
77
|
+
for (const node of nodes) {
|
|
78
|
+
next.set(
|
|
79
|
+
node,
|
|
80
|
+
Number(next.get(node) || 0) + damping * dangling * Number(seeds.get(node) || 0),
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
scores = next
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const semantic = new Map(
|
|
88
|
+
(options.semanticResults || []).map((item) => [normalizePath(item.path), Number(item.score || 0)]),
|
|
89
|
+
)
|
|
90
|
+
const maxRank = Math.max(1e-12, ...scores.values())
|
|
91
|
+
const maxSemantic = Math.max(1, ...semantic.values())
|
|
92
|
+
|
|
93
|
+
return nodes
|
|
94
|
+
.map((file) => {
|
|
95
|
+
const pageRank = Number(scores.get(file) || 0)
|
|
96
|
+
const seed = Number(seeds.get(file) || 0)
|
|
97
|
+
const semanticScore = Number(semantic.get(file) || 0)
|
|
98
|
+
const normalizedRank = pageRank / maxRank
|
|
99
|
+
const normalizedSemantic = semanticScore / maxSemantic
|
|
100
|
+
const score = normalizedRank * 60 + normalizedSemantic * 40
|
|
101
|
+
return {
|
|
102
|
+
path: file,
|
|
103
|
+
score: Number(score.toFixed(6)),
|
|
104
|
+
pageRank: Number(pageRank.toFixed(10)),
|
|
105
|
+
personalization: Number(seed.toFixed(10)),
|
|
106
|
+
semanticScore,
|
|
107
|
+
reasons: [
|
|
108
|
+
semanticScore > 0 ? "semantic-seed" : null,
|
|
109
|
+
(options.declared || []).map(normalizePath).includes(file) ? "declared" : null,
|
|
110
|
+
(options.changed || []).map(normalizePath).includes(file) ? "changed" : null,
|
|
111
|
+
Number(neighbors.get(file)?.size || 0) > 0 ? "dependency-graph" : null,
|
|
112
|
+
].filter(Boolean),
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
.filter((item) => item.score > 0)
|
|
116
|
+
.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
117
|
+
.slice(0, Math.max(1, Math.min(100, Number(options.limit || 32))))
|
|
118
|
+
}
|
package/lib/context-manifest.mjs
CHANGED
|
@@ -2,9 +2,11 @@ import { existsSync } from "node:fs"
|
|
|
2
2
|
import { readFile, stat } from "node:fs/promises"
|
|
3
3
|
import { spawnSync } from "node:child_process"
|
|
4
4
|
import path from "node:path"
|
|
5
|
-
import {
|
|
5
|
+
import { buildRepoGraphCached } from "./repo-graph.mjs"
|
|
6
6
|
import { taskFiles } from "./task-graph.mjs"
|
|
7
|
-
import { querySemanticIndex } from "./semantic-index.mjs"
|
|
7
|
+
import { buildSemanticIndexCached, querySemanticIndex } from "./semantic-index.mjs"
|
|
8
|
+
import { queryContextHierarchy } from "./hierarchical-context.mjs"
|
|
9
|
+
import { rankContextGraph } from "./context-graph-rank.mjs"
|
|
8
10
|
|
|
9
11
|
const ROOT_INSTRUCTIONS = [
|
|
10
12
|
"AGENTS.md",
|
|
@@ -247,7 +249,9 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
247
249
|
const evidenceBudget = options.evidenceBudget || null
|
|
248
250
|
const declared = taskFiles(task)
|
|
249
251
|
const terms = taskTerms(task)
|
|
250
|
-
const changed =
|
|
252
|
+
const changed = Array.isArray(options.changedFiles)
|
|
253
|
+
? options.changedFiles.map((file) => String(file || "").replaceAll("\\", "/")).filter(Boolean)
|
|
254
|
+
: gitChangedFiles(root)
|
|
251
255
|
const strategy = String(options.strategy || "semantic+graph+git")
|
|
252
256
|
const queryText = [
|
|
253
257
|
task?.title,
|
|
@@ -256,15 +260,45 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
256
260
|
...(task?.verification || []),
|
|
257
261
|
].filter(Boolean).join(" ")
|
|
258
262
|
|
|
263
|
+
const semanticSnapshot = await buildSemanticIndexCached(root, {
|
|
264
|
+
maxFiles: options.semanticMaxFiles ?? 6000,
|
|
265
|
+
maxDepth: options.semanticMaxDepth ?? 14,
|
|
266
|
+
workspaceFingerprint: options.workspaceFingerprint,
|
|
267
|
+
}).catch(() => null)
|
|
268
|
+
|
|
269
|
+
const hierarchy = await queryContextHierarchy(root, queryText, {
|
|
270
|
+
declaredFiles: declared,
|
|
271
|
+
maxScopes: options.hierarchyMaxScopes ?? 6,
|
|
272
|
+
maxFiles: options.semanticMaxFiles ?? 6000,
|
|
273
|
+
builtIndex: semanticSnapshot || undefined,
|
|
274
|
+
}).catch(() => null)
|
|
275
|
+
const hierarchyScopes = hierarchy?.scopes?.map((item) => item.path) || []
|
|
276
|
+
|
|
259
277
|
const semantic = await querySemanticIndex(root, queryText, {
|
|
260
278
|
limit: 32,
|
|
261
279
|
maxFiles: options.semanticMaxFiles ?? 6000,
|
|
280
|
+
builtIndex: semanticSnapshot || undefined,
|
|
262
281
|
}).catch(() => null)
|
|
263
|
-
const
|
|
282
|
+
const scopedSemanticResults = (semantic?.results || []).map((item) => {
|
|
283
|
+
const scopeRank = hierarchyScopes.findIndex((scope) =>
|
|
284
|
+
scope === "." || item.path === scope || item.path.startsWith(scope + "/"),
|
|
285
|
+
)
|
|
286
|
+
const scopeBoost = scopeRank >= 0 ? Math.max(2, 12 - scopeRank * 2) : 0
|
|
287
|
+
return {
|
|
288
|
+
...item,
|
|
289
|
+
baseScore: item.score,
|
|
290
|
+
scopeBoost,
|
|
291
|
+
score: Number((Number(item.score || 0) + scopeBoost).toFixed(3)),
|
|
292
|
+
}
|
|
293
|
+
}).sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
|
|
294
|
+
const semanticPaths = scopedSemanticResults.map((item) => item.path)
|
|
264
295
|
|
|
265
296
|
const useGraph = strategy.includes("graph")
|
|
266
297
|
const graph = useGraph
|
|
267
|
-
? await
|
|
298
|
+
? await buildRepoGraphCached(root, {
|
|
299
|
+
maxFiles: options.maxFiles ?? 2500,
|
|
300
|
+
workspaceFingerprint: options.workspaceFingerprint,
|
|
301
|
+
})
|
|
268
302
|
: { nodes: [], edges: [], hotspots: [], scannedFiles: 0, truncated: false }
|
|
269
303
|
const nodeMap = new Map(graph.nodes.map((node) => [node.path, node]))
|
|
270
304
|
const incoming = new Map()
|
|
@@ -297,17 +331,46 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
297
331
|
.map((item) => item.file)
|
|
298
332
|
|
|
299
333
|
const instructions = await nearbyInstructions(root, declared)
|
|
300
|
-
const
|
|
301
|
-
?
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
334
|
+
const graphRanked = useGraph
|
|
335
|
+
? rankContextGraph(graph, {
|
|
336
|
+
semanticResults: scopedSemanticResults,
|
|
337
|
+
declared,
|
|
338
|
+
changed,
|
|
339
|
+
limit: 32,
|
|
340
|
+
})
|
|
341
|
+
: []
|
|
342
|
+
|
|
343
|
+
const semanticByPath = new Map(scopedSemanticResults.map((item) => [item.path, item]))
|
|
344
|
+
const ranked = graphRanked.length
|
|
345
|
+
? graphRanked.map((item) => {
|
|
346
|
+
const semanticItem = semanticByPath.get(item.path)
|
|
347
|
+
return {
|
|
348
|
+
path: item.path,
|
|
349
|
+
score: item.score,
|
|
350
|
+
pageRank: item.pageRank,
|
|
351
|
+
personalization: item.personalization,
|
|
352
|
+
baseScore: semanticItem?.baseScore ?? semanticItem?.score ?? item.semanticScore,
|
|
353
|
+
scopeBoost: semanticItem?.scopeBoost || 0,
|
|
354
|
+
matches: [...new Set([...(semanticItem?.reasons || []), ...(item.reasons || [])])],
|
|
355
|
+
symbolHits: semanticItem?.definitions || [],
|
|
356
|
+
evidenceLevel: semanticItem
|
|
357
|
+
? String(semantic?.evidenceLevel || "syntax-aware-lexical") + "+dependency-graph-rank"
|
|
358
|
+
: "dependency-graph-rank",
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
: scopedSemanticResults.length
|
|
362
|
+
? scopedSemanticResults.map((item) => ({
|
|
363
|
+
path: item.path,
|
|
364
|
+
score: item.score,
|
|
365
|
+
baseScore: item.baseScore ?? item.score,
|
|
366
|
+
scopeBoost: item.scopeBoost || 0,
|
|
367
|
+
matches: item.reasons || [],
|
|
368
|
+
symbolHits: item.definitions || [],
|
|
369
|
+
evidenceLevel: semantic.evidenceLevel,
|
|
370
|
+
}))
|
|
371
|
+
: useGraph
|
|
372
|
+
? await rankedReferences(root, graph.nodes, terms, declared, changed)
|
|
373
|
+
: []
|
|
311
374
|
const changedRelevant = changed
|
|
312
375
|
.filter((file) =>
|
|
313
376
|
declared.includes(file) ||
|
|
@@ -372,6 +435,18 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
372
435
|
rankedReferences: ranked,
|
|
373
436
|
files,
|
|
374
437
|
excerpts,
|
|
438
|
+
hierarchy: hierarchy ? {
|
|
439
|
+
schemaVersion: hierarchy.schemaVersion,
|
|
440
|
+
levels: hierarchy.levels,
|
|
441
|
+
scopes: hierarchy.scopes.slice(0, 6).map((item) => ({
|
|
442
|
+
path: item.path,
|
|
443
|
+
score: item.score,
|
|
444
|
+
reasons: item.reasons,
|
|
445
|
+
fileCount: item.fileCount,
|
|
446
|
+
l0: item.l0,
|
|
447
|
+
l1: item.l1,
|
|
448
|
+
})),
|
|
449
|
+
} : null,
|
|
375
450
|
semantic: semantic ? {
|
|
376
451
|
evidenceLevel: semantic.evidenceLevel,
|
|
377
452
|
resultCount: semantic.results.length,
|
|
@@ -382,11 +457,15 @@ export async function buildContextManifest(root, task, options = {}) {
|
|
|
382
457
|
scannedFiles: graph.scannedFiles,
|
|
383
458
|
truncated: graph.truncated,
|
|
384
459
|
hotspots: graph.hotspots.slice(0, 12),
|
|
460
|
+
ranking: graphRanked.length ? {
|
|
461
|
+
strategy: "personalized-dependency-graph-rank",
|
|
462
|
+
resultCount: graphRanked.length,
|
|
463
|
+
top: graphRanked.slice(0, 12),
|
|
464
|
+
} : null,
|
|
385
465
|
},
|
|
386
466
|
budget,
|
|
387
467
|
evidenceBudget,
|
|
388
468
|
categoryRemaining,
|
|
389
469
|
used: budget - remaining,
|
|
390
470
|
}
|
|
391
|
-
}
|
|
392
|
-
|
|
471
|
+
}
|