opencode-agent-skill 13.0.0-beta.2 → 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.
- package/README.md +1556 -607
- package/bin/ocskill.mjs +172 -24
- package/docs/OPENCODE-COMPAT.md +34 -97
- package/docs/PI-COMPAT.md +188 -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 +336 -0
- package/lib/capability-registry.mjs +9 -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/dynamic-workflow.mjs +3 -1
- package/lib/evidence-store.mjs +82 -1
- package/lib/hierarchical-context.mjs +215 -0
- package/lib/memory-engine.mjs +465 -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 +2 -0
- package/lib/repo-graph.mjs +53 -2
- 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 +66 -5
- package/lib/task-policy.mjs +235 -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 +238 -0
- package/pi/extensions/ues.ts +3200 -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 +184 -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
package/lib/prompt-cache.mjs
CHANGED
|
@@ -29,6 +29,8 @@ export function buildPromptEnvelope(input = {}) {
|
|
|
29
29
|
const dynamic = {
|
|
30
30
|
task: input.task || {},
|
|
31
31
|
evidence: input.evidence || [],
|
|
32
|
+
memories: input.memories || [],
|
|
33
|
+
contextHints: input.contextHints || {},
|
|
32
34
|
recentFailure: input.recentFailure || null,
|
|
33
35
|
nextAction: input.nextAction || null,
|
|
34
36
|
recentMessages: input.recentMessages || [],
|
package/lib/repo-graph.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { readFile, readdir, stat } from "node:fs/promises"
|
|
2
2
|
import path from "node:path"
|
|
3
|
+
import { runtimeWorkspaceFingerprint } from "./workspace-fingerprint.mjs"
|
|
3
4
|
|
|
4
5
|
const SKIP = new Set([
|
|
5
6
|
".git", "node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
|
|
6
|
-
"Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "
|
|
7
|
-
".ues-work",
|
|
7
|
+
"Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "obj",
|
|
8
|
+
".ues-work", ".ues-cache", ".ues-traces", ".ues-learning", ".ues-dashboard",
|
|
9
|
+
".ues-sandboxes", ".ues-memory", ".ues-evals",
|
|
8
10
|
])
|
|
9
11
|
|
|
10
12
|
const SOURCE_EXTENSIONS = new Set([
|
|
@@ -133,3 +135,52 @@ export async function buildRepoGraph(root = process.cwd(), options = {}) {
|
|
|
133
135
|
.slice(0, 50),
|
|
134
136
|
}
|
|
135
137
|
}
|
|
138
|
+
|
|
139
|
+
const RUNTIME_GRAPH_CACHE = new Map()
|
|
140
|
+
const RUNTIME_GRAPH_INFLIGHT = new Map()
|
|
141
|
+
|
|
142
|
+
export async function buildRepoGraphCached(root = process.cwd(), options = {}) {
|
|
143
|
+
root = path.resolve(root)
|
|
144
|
+
const maxFiles = Number(options.maxFiles ?? 2500)
|
|
145
|
+
const maxDepth = Number(options.maxDepth ?? 8)
|
|
146
|
+
let fingerprint = String(options.workspaceFingerprint || "")
|
|
147
|
+
if (!fingerprint || fingerprint === "unknown") {
|
|
148
|
+
try {
|
|
149
|
+
fingerprint = runtimeWorkspaceFingerprint(root)
|
|
150
|
+
} catch {
|
|
151
|
+
return buildRepoGraph(root, options)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const key = [root, fingerprint, maxFiles, maxDepth].join("\u0000")
|
|
155
|
+
if (RUNTIME_GRAPH_CACHE.has(key)) {
|
|
156
|
+
const value = RUNTIME_GRAPH_CACHE.get(key)
|
|
157
|
+
RUNTIME_GRAPH_CACHE.delete(key)
|
|
158
|
+
RUNTIME_GRAPH_CACHE.set(key, value)
|
|
159
|
+
return value
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (RUNTIME_GRAPH_INFLIGHT.has(key)) {
|
|
163
|
+
return RUNTIME_GRAPH_INFLIGHT.get(key)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const pending = buildRepoGraph(root, options)
|
|
167
|
+
RUNTIME_GRAPH_INFLIGHT.set(key, pending)
|
|
168
|
+
let graph
|
|
169
|
+
try {
|
|
170
|
+
graph = await pending
|
|
171
|
+
} finally {
|
|
172
|
+
RUNTIME_GRAPH_INFLIGHT.delete(key)
|
|
173
|
+
}
|
|
174
|
+
RUNTIME_GRAPH_CACHE.set(key, graph)
|
|
175
|
+
while (RUNTIME_GRAPH_CACHE.size > 8) {
|
|
176
|
+
const oldest = RUNTIME_GRAPH_CACHE.keys().next().value
|
|
177
|
+
if (!oldest) break
|
|
178
|
+
RUNTIME_GRAPH_CACHE.delete(oldest)
|
|
179
|
+
}
|
|
180
|
+
return graph
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function clearRepoGraphRuntimeCache() {
|
|
184
|
+
RUNTIME_GRAPH_CACHE.clear()
|
|
185
|
+
RUNTIME_GRAPH_INFLIGHT.clear()
|
|
186
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import os from "node:os"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
function xdgConfigRoot() {
|
|
6
|
+
return process.env.XDG_CONFIG_HOME
|
|
7
|
+
? path.resolve(process.env.XDG_CONFIG_HOME)
|
|
8
|
+
: path.join(os.homedir(), ".config")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getUesConfigDir() {
|
|
12
|
+
if (process.env.UES_CONFIG_DIR) return path.resolve(process.env.UES_CONFIG_DIR)
|
|
13
|
+
|
|
14
|
+
const nativeDir = path.join(xdgConfigRoot(), "ues")
|
|
15
|
+
const legacyDir = process.env.OPENCODE_CONFIG_DIR
|
|
16
|
+
? path.resolve(process.env.OPENCODE_CONFIG_DIR)
|
|
17
|
+
: path.join(xdgConfigRoot(), "opencode")
|
|
18
|
+
|
|
19
|
+
const nativePolicy = path.join(nativeDir, ".ues", "model-policy.json")
|
|
20
|
+
const legacyPolicy = path.join(legacyDir, ".ues", "model-policy.json")
|
|
21
|
+
|
|
22
|
+
if (existsSync(nativePolicy)) return nativeDir
|
|
23
|
+
if (existsSync(legacyPolicy)) return legacyDir
|
|
24
|
+
return nativeDir
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getLegacyOpenCodeConfigDir() {
|
|
28
|
+
return process.env.OPENCODE_CONFIG_DIR
|
|
29
|
+
? path.resolve(process.env.OPENCODE_CONFIG_DIR)
|
|
30
|
+
: path.join(xdgConfigRoot(), "opencode")
|
|
31
|
+
}
|
package/lib/safety.mjs
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const RULES = [
|
|
2
|
+
{ id: "git-force", pattern: /\bgit\s+(?:push\b[^\n]*(?:--force(?:-with-lease)?|(?:^|\s)-f(?:\s|$))|reset\s+--hard|clean\s+-[^\n]*f)/i },
|
|
3
|
+
{ id: "history-rewrite", pattern: /\bgit\s+(?:rebase\b|filter-branch\b|filter-repo\b)/i },
|
|
4
|
+
{ id: "publish", pattern: /\b(?:npm\s+publish|pnpm\s+publish|yarn\s+npm\s+publish)\b/i },
|
|
5
|
+
{ id: "destructive-files", pattern: /(?:^|[;&|]\s*)(?:rm\s+-[^\n]*r[^\n]*f|rmdir\s+\/s|del\s+\/s|remove-item\b[^\n]*-recurse[^\n]*-force)/i },
|
|
6
|
+
{ id: "database-drop", pattern: /\b(?:drop\s+(?:database|schema|table)|truncate\s+table)\b/i },
|
|
7
|
+
{ id: "deployment", pattern: /\b(?:kubectl\s+(?:delete|apply)|terraform\s+(?:apply|destroy)|helm\s+(?:install|upgrade|uninstall))\b/i },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
export function shellCommandSegments(command) {
|
|
11
|
+
const value = String(command || "")
|
|
12
|
+
const segments = []
|
|
13
|
+
let current = ""
|
|
14
|
+
let quote = null
|
|
15
|
+
let escaped = false
|
|
16
|
+
let depth = 0
|
|
17
|
+
|
|
18
|
+
const push = (operator = null) => {
|
|
19
|
+
const text = current.trim()
|
|
20
|
+
if (text) segments.push({ text, operator })
|
|
21
|
+
current = ""
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
25
|
+
const char = value[index]
|
|
26
|
+
const next = value[index + 1] || ""
|
|
27
|
+
|
|
28
|
+
if (escaped) {
|
|
29
|
+
current += char
|
|
30
|
+
escaped = false
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
if (char === "\\" && quote !== "'") {
|
|
34
|
+
current += char
|
|
35
|
+
escaped = true
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
if (quote) {
|
|
39
|
+
current += char
|
|
40
|
+
if (char === quote) quote = null
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
if (char === "'" || char === '"') {
|
|
44
|
+
quote = char
|
|
45
|
+
current += char
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if (char === "(" || char === "{" || char === "[") {
|
|
49
|
+
depth += 1
|
|
50
|
+
current += char
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
if (char === ")" || char === "}" || char === "]") {
|
|
54
|
+
depth = Math.max(0, depth - 1)
|
|
55
|
+
current += char
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (depth === 0 && char === "&" && next === "&") {
|
|
60
|
+
push("&&")
|
|
61
|
+
index += 1
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
if (depth === 0 && char === "|" && next === "|") {
|
|
65
|
+
push("||")
|
|
66
|
+
index += 1
|
|
67
|
+
continue
|
|
68
|
+
}
|
|
69
|
+
if (depth === 0 && (char === ";" || char === "|")) {
|
|
70
|
+
push(char)
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
if (depth === 0 && char === "\n") {
|
|
74
|
+
push("\n")
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
current += char
|
|
78
|
+
}
|
|
79
|
+
push(null)
|
|
80
|
+
return segments
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function destructiveShellAnalysis(command) {
|
|
84
|
+
const value = String(command || "")
|
|
85
|
+
const segments = shellCommandSegments(value)
|
|
86
|
+
const findings = []
|
|
87
|
+
|
|
88
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
89
|
+
const segment = segments[index]
|
|
90
|
+
for (const rule of RULES) {
|
|
91
|
+
// Clone regex state defensively if future rules add the global flag.
|
|
92
|
+
rule.pattern.lastIndex = 0
|
|
93
|
+
if (!rule.pattern.test(segment.text)) continue
|
|
94
|
+
findings.push({
|
|
95
|
+
id: rule.id,
|
|
96
|
+
segmentIndex: index,
|
|
97
|
+
segment: segment.text,
|
|
98
|
+
operator: segment.operator,
|
|
99
|
+
})
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Retain a whole-command fallback so nested shell syntax that the bounded
|
|
105
|
+
// splitter intentionally does not fully parse still fails closed.
|
|
106
|
+
if (!findings.length) {
|
|
107
|
+
for (const rule of RULES) {
|
|
108
|
+
rule.pattern.lastIndex = 0
|
|
109
|
+
if (rule.pattern.test(value)) {
|
|
110
|
+
findings.push({ id: rule.id, segmentIndex: null, segment: value, operator: null })
|
|
111
|
+
break
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
risky: findings.length > 0,
|
|
118
|
+
id: findings[0]?.id || null,
|
|
119
|
+
findings,
|
|
120
|
+
segmentCount: segments.length,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function destructiveShellRisk(command) {
|
|
125
|
+
const analysis = destructiveShellAnalysis(command)
|
|
126
|
+
return {
|
|
127
|
+
risky: analysis.risky,
|
|
128
|
+
id: analysis.id,
|
|
129
|
+
segmentIndex: analysis.findings[0]?.segmentIndex ?? null,
|
|
130
|
+
segment: analysis.findings[0]?.segment || null,
|
|
131
|
+
}
|
|
132
|
+
}
|
package/lib/semantic-index.mjs
CHANGED
|
@@ -11,8 +11,8 @@ const SOURCE_EXTENSIONS = new Set([
|
|
|
11
11
|
])
|
|
12
12
|
const SKIP_DIRS = new Set([
|
|
13
13
|
".git", "node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
|
|
14
|
-
"Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "
|
|
15
|
-
".ues-work", ".ues-learning", ".ues-dashboard", ".ues-sandboxes", ".ues-cache", ".ues-traces",
|
|
14
|
+
"Pods", "DerivedData", ".gradle", ".idea", ".cache", ".turbo", "target", "obj",
|
|
15
|
+
".ues-work", ".ues-learning", ".ues-memory", ".ues-dashboard", ".ues-sandboxes", ".ues-cache", ".ues-traces",
|
|
16
16
|
])
|
|
17
17
|
const STOP_IDENTIFIERS = new Set([
|
|
18
18
|
"const","let","var","function","class","interface","type","export","import","from","return",
|
|
@@ -233,7 +233,7 @@ function queryTerms(query) {
|
|
|
233
233
|
export async function querySemanticIndex(root, query, options = {}) {
|
|
234
234
|
const terms = queryTerms(query)
|
|
235
235
|
if (!terms.length) return { query: String(query || ""), terms: [], results: [], stats: null }
|
|
236
|
-
const built = await buildSemanticIndex(root, options)
|
|
236
|
+
const built = options.builtIndex || await buildSemanticIndex(root, options)
|
|
237
237
|
const lowerTerms = terms.map((term) => term.toLowerCase())
|
|
238
238
|
const results = []
|
|
239
239
|
|
|
@@ -316,3 +316,52 @@ export async function semanticIndexStatus(root = process.cwd()) {
|
|
|
316
316
|
truncated: Boolean(cached?.truncated),
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
|
+
|
|
320
|
+
const RUNTIME_SEMANTIC_CACHE = new Map()
|
|
321
|
+
const RUNTIME_SEMANTIC_INFLIGHT = new Map()
|
|
322
|
+
|
|
323
|
+
export async function buildSemanticIndexCached(root = process.cwd(), options = {}) {
|
|
324
|
+
root = path.resolve(root)
|
|
325
|
+
const fingerprint = String(options.workspaceFingerprint || "")
|
|
326
|
+
if (!fingerprint || fingerprint === "unknown") {
|
|
327
|
+
return buildSemanticIndex(root, options)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const maxFiles = Number(options.maxFiles ?? 6000)
|
|
331
|
+
const maxDepth = Number(options.maxDepth ?? 14)
|
|
332
|
+
const maxBytes = Number(options.maxBytes ?? 768 * 1024)
|
|
333
|
+
const key = [root, fingerprint, maxFiles, maxDepth, maxBytes].join("\u0000")
|
|
334
|
+
if (RUNTIME_SEMANTIC_CACHE.has(key)) {
|
|
335
|
+
const value = RUNTIME_SEMANTIC_CACHE.get(key)
|
|
336
|
+
RUNTIME_SEMANTIC_CACHE.delete(key)
|
|
337
|
+
RUNTIME_SEMANTIC_CACHE.set(key, value)
|
|
338
|
+
return { ...value, runtimeCacheHit: true }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (RUNTIME_SEMANTIC_INFLIGHT.has(key)) {
|
|
342
|
+
const value = await RUNTIME_SEMANTIC_INFLIGHT.get(key)
|
|
343
|
+
return { ...value, runtimeCacheHit: true, runtimeCacheCoalesced: true }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const pending = buildSemanticIndex(root, options)
|
|
347
|
+
.then((built) => ({ ...built, runtimeCacheHit: false }))
|
|
348
|
+
RUNTIME_SEMANTIC_INFLIGHT.set(key, pending)
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const value = await pending
|
|
352
|
+
RUNTIME_SEMANTIC_CACHE.set(key, value)
|
|
353
|
+
while (RUNTIME_SEMANTIC_CACHE.size > 6) {
|
|
354
|
+
const oldest = RUNTIME_SEMANTIC_CACHE.keys().next().value
|
|
355
|
+
if (!oldest) break
|
|
356
|
+
RUNTIME_SEMANTIC_CACHE.delete(oldest)
|
|
357
|
+
}
|
|
358
|
+
return value
|
|
359
|
+
} finally {
|
|
360
|
+
RUNTIME_SEMANTIC_INFLIGHT.delete(key)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function clearSemanticIndexRuntimeCache() {
|
|
365
|
+
RUNTIME_SEMANTIC_CACHE.clear()
|
|
366
|
+
RUNTIME_SEMANTIC_INFLIGHT.clear()
|
|
367
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { readFile } from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
7
|
+
const SKILLS_ROOT = path.join(PACKAGE_ROOT, "global-config", "skills")
|
|
8
|
+
const SKILL_SOURCE_CACHE = new Map()
|
|
9
|
+
const COMPILED_SKILL_CACHE = new Map()
|
|
10
|
+
|
|
11
|
+
const DOMAIN_SKILLS = Object.freeze({
|
|
12
|
+
"auth-security": "auth-security",
|
|
13
|
+
payment: "payment-engineering",
|
|
14
|
+
database: "database-engineering",
|
|
15
|
+
"api-contract": "api-contract",
|
|
16
|
+
"react-native": "react-native-engineering",
|
|
17
|
+
nextjs: "nextjs-engineering",
|
|
18
|
+
react: "react-engineering",
|
|
19
|
+
devops: "devops-engineering",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const ROLE_SKILLS = Object.freeze({
|
|
23
|
+
architect: ["software-architect", "task-planner"],
|
|
24
|
+
"codebase-mapper": ["repo-explorer", "context-engineering"],
|
|
25
|
+
critic: ["code-review", "change-impact-analysis"],
|
|
26
|
+
debugger: ["bug-diagnosis", "test-verification"],
|
|
27
|
+
executor: ["implementation-engineer"],
|
|
28
|
+
"integration-verifier": ["test-verification", "change-impact-analysis"],
|
|
29
|
+
"merge-arbiter": ["git-safety", "change-impact-analysis"],
|
|
30
|
+
"plan-checker": ["task-planner", "change-impact-analysis"],
|
|
31
|
+
researcher: ["research-verification"],
|
|
32
|
+
reviewer: ["code-review"],
|
|
33
|
+
verifier: ["test-verification"],
|
|
34
|
+
"visual-verifier": ["visual-fidelity", "responsive-verification", "browser-qa"],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
function stripFrontmatter(raw) {
|
|
38
|
+
return String(raw || "").replace(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n/, "")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function priorityLine(line) {
|
|
42
|
+
return /^#{1,4}\s/.test(line) ||
|
|
43
|
+
/^[-*]\s+/.test(line) ||
|
|
44
|
+
/\b(must|never|always|verify|prefer|avoid|require|do not|don't|fail|evidence|security|safety|rollback|idempot|transaction|accessib)/i.test(line)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function compileText(raw, maxChars) {
|
|
48
|
+
const source = stripFrontmatter(raw)
|
|
49
|
+
const lines = source.split(/\r?\n/).map((line) => line.trimEnd())
|
|
50
|
+
const selected = []
|
|
51
|
+
let chars = 0
|
|
52
|
+
|
|
53
|
+
// Keep a short orientation prefix, then the highest-value rules/checklists.
|
|
54
|
+
for (const line of lines.slice(0, 20)) {
|
|
55
|
+
if (!line.trim()) continue
|
|
56
|
+
if (chars + line.length + 1 > Math.floor(maxChars * 0.35)) break
|
|
57
|
+
selected.push(line)
|
|
58
|
+
chars += line.length + 1
|
|
59
|
+
}
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
if (!line.trim() || !priorityLine(line) || selected.includes(line)) continue
|
|
62
|
+
if (chars + line.length + 1 > maxChars) break
|
|
63
|
+
selected.push(line)
|
|
64
|
+
chars += line.length + 1
|
|
65
|
+
}
|
|
66
|
+
return selected.join("\n").slice(0, maxChars)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function selectSkillNames(taskPolicy = {}, role = "executor", options = {}) {
|
|
70
|
+
const maxSkills = Math.max(1, Math.min(5, Number(options.maxSkills || taskPolicy.maxSkills || 3)))
|
|
71
|
+
const names = []
|
|
72
|
+
for (const name of ROLE_SKILLS[role] || []) names.push(name)
|
|
73
|
+
for (const domain of taskPolicy.domains || []) {
|
|
74
|
+
const mapped = DOMAIN_SKILLS[domain]
|
|
75
|
+
if (mapped) names.push(mapped)
|
|
76
|
+
}
|
|
77
|
+
return [...new Set(names)].slice(0, maxSkills)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function skillSource(name) {
|
|
81
|
+
if (SKILL_SOURCE_CACHE.has(name)) return SKILL_SOURCE_CACHE.get(name)
|
|
82
|
+
const file = path.join(SKILLS_ROOT, name, "SKILL.md")
|
|
83
|
+
if (!existsSync(file)) {
|
|
84
|
+
SKILL_SOURCE_CACHE.set(name, "")
|
|
85
|
+
return ""
|
|
86
|
+
}
|
|
87
|
+
const raw = await readFile(file, "utf8").catch(() => "")
|
|
88
|
+
SKILL_SOURCE_CACHE.set(name, raw)
|
|
89
|
+
return raw
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function compileSkillContext(taskPolicy = {}, role = "executor", options = {}) {
|
|
93
|
+
const names = selectSkillNames(taskPolicy, role, options)
|
|
94
|
+
const totalChars = Math.max(800, Math.min(8_000, Number(options.totalChars || 3_200)))
|
|
95
|
+
const perSkill = Math.max(450, Math.floor(totalChars / Math.max(1, names.length)))
|
|
96
|
+
const cacheKey = JSON.stringify([role, names, totalChars, perSkill])
|
|
97
|
+
if (COMPILED_SKILL_CACHE.has(cacheKey)) {
|
|
98
|
+
return { ...COMPILED_SKILL_CACHE.get(cacheKey), cacheHit: true }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const skills = []
|
|
102
|
+
for (const name of names) {
|
|
103
|
+
const raw = await skillSource(name)
|
|
104
|
+
if (!raw) continue
|
|
105
|
+
const text = compileText(raw, perSkill)
|
|
106
|
+
if (text) skills.push({ name, text })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = {
|
|
110
|
+
schemaVersion: 1,
|
|
111
|
+
role,
|
|
112
|
+
requested: names,
|
|
113
|
+
loaded: skills.map((item) => item.name),
|
|
114
|
+
chars: skills.reduce((sum, item) => sum + item.text.length, 0),
|
|
115
|
+
text: skills
|
|
116
|
+
.map((item) => `### Skill: ${item.name}\n${item.text}`)
|
|
117
|
+
.join("\n\n")
|
|
118
|
+
.slice(0, totalChars),
|
|
119
|
+
cacheHit: false,
|
|
120
|
+
}
|
|
121
|
+
COMPILED_SKILL_CACHE.set(cacheKey, result)
|
|
122
|
+
return result
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function clearSkillCompilerCache() {
|
|
126
|
+
SKILL_SOURCE_CACHE.clear()
|
|
127
|
+
COMPILED_SKILL_CACHE.clear()
|
|
128
|
+
}
|
package/lib/skill-quality.mjs
CHANGED
|
@@ -15,9 +15,53 @@ function similarity(a, b) {
|
|
|
15
15
|
return union.size ? intersection / union.size : 0
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export function validateFrontmatterSource(source, options = {}) {
|
|
19
|
+
const text = String(source || "")
|
|
20
|
+
const errors = []
|
|
21
|
+
if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) {
|
|
22
|
+
return { valid: false, errors: [{ issue: "missing-frontmatter-open", file: options.file || null }] }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const match = text.match(/^---\s*\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/)
|
|
26
|
+
if (!match) {
|
|
27
|
+
return { valid: false, errors: [{ issue: "missing-frontmatter-close", file: options.file || null }] }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const lines = match[1].split(/\r?\n/)
|
|
31
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
32
|
+
const line = lines[index]
|
|
33
|
+
if (!line.trim() || /^\s/.test(line) || line.trimStart().startsWith("#")) continue
|
|
34
|
+
if (/\t/.test(line)) {
|
|
35
|
+
errors.push({ issue: "frontmatter-tab", line: index + 2, file: options.file || null })
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
const field = line.match(/^([A-Za-z0-9_-]+):(?:\s*(.*))?$/)
|
|
39
|
+
if (!field) {
|
|
40
|
+
errors.push({ issue: "invalid-frontmatter-line", line: index + 2, file: options.file || null })
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
const value = String(field[2] || "").trim()
|
|
44
|
+
if (!value) continue
|
|
45
|
+
const quoted = (value.startsWith('"') && value.endsWith('"')) ||
|
|
46
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
47
|
+
const blockScalar = value === "|" || value === ">"
|
|
48
|
+
const flowValue = value.startsWith("[") || value.startsWith("{")
|
|
49
|
+
if (!quoted && !blockScalar && !flowValue && /:\s/.test(value)) {
|
|
50
|
+
errors.push({
|
|
51
|
+
issue: "unquoted-colon-in-scalar",
|
|
52
|
+
field: field[1],
|
|
53
|
+
line: index + 2,
|
|
54
|
+
file: options.file || null,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { valid: errors.length === 0, errors }
|
|
60
|
+
}
|
|
61
|
+
|
|
18
62
|
function frontmatter(source) {
|
|
19
|
-
const name = source.match(/^name:\s*([^\r\n]+)/m)?.[1]?.trim() || null
|
|
20
|
-
const description = source.match(/^description:\s*([^\r\n]+)/m)?.[1]?.trim() || null
|
|
63
|
+
const name = source.match(/^name:\s*([^\r\n]+)/m)?.[1]?.trim()?.replace(/^(["'])(.*)\1$/, "$2") || null
|
|
64
|
+
const description = source.match(/^description:\s*([^\r\n]+)/m)?.[1]?.trim()?.replace(/^(["'])(.*)\1$/, "$2") || null
|
|
21
65
|
return { name, description }
|
|
22
66
|
}
|
|
23
67
|
|
|
@@ -36,6 +80,8 @@ export async function lintSkillCatalog(root = process.cwd(), options = {}) {
|
|
|
36
80
|
continue
|
|
37
81
|
}
|
|
38
82
|
const source = await readFile(file, "utf8")
|
|
83
|
+
const yaml = validateFrontmatterSource(source, { file })
|
|
84
|
+
for (const error of yaml.errors) errors.push({ skill: entry.name, ...error })
|
|
39
85
|
const meta = frontmatter(source)
|
|
40
86
|
const body = source.replace(/^---[\s\S]*?---\s*/m, "")
|
|
41
87
|
const row = {
|
package/lib/task-engine.mjs
CHANGED
|
@@ -15,6 +15,8 @@ import { putEvidence } from "./evidence-store.mjs"
|
|
|
15
15
|
import { inferTaskCapabilities } from "./capability-registry.mjs"
|
|
16
16
|
import { buildPromptEnvelope } from "./prompt-cache.mjs"
|
|
17
17
|
import { externalizeContextExcerpts } from "./context-engine-v11.mjs"
|
|
18
|
+
import { recordVerifiedTaskMemory, retrieveMemories } from "./memory-engine.mjs"
|
|
19
|
+
import { capabilityFabricStatus } from "./capability-fabric.mjs"
|
|
18
20
|
import { assertActivePlanScope, persistPlanScope } from "./work-plan-scope.mjs"
|
|
19
21
|
import { classifyDecisionPolicy } from "./decision-policy.mjs"
|
|
20
22
|
import { readTextAuto } from "./text-encoding.mjs"
|
|
@@ -174,7 +176,7 @@ function gitCapture(root, args) {
|
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
const FINGERPRINT_SKIP_DIRS = new Set([
|
|
177
|
-
".git", ".ues-work", ".ues-learning", ".ues-dashboard", ".ues-sandboxes", ".ues-cache", ".ues-traces",
|
|
179
|
+
".git", ".ues-work", ".ues-learning", ".ues-dashboard", ".ues-sandboxes", ".ues-cache", ".ues-traces", ".ues-memory",
|
|
178
180
|
"node_modules", ".next", "dist", "build", "coverage", ".venv", "venv",
|
|
179
181
|
"Pods", "DerivedData", ".gradle", ".cache", ".turbo", "target", "bin", "obj",
|
|
180
182
|
])
|
|
@@ -256,9 +258,9 @@ export function workspaceFingerprint(root) {
|
|
|
256
258
|
const parts = []
|
|
257
259
|
const commands = [
|
|
258
260
|
["rev-parse", "HEAD"],
|
|
259
|
-
["status", "--porcelain=v1", "--untracked-files=all", "--", ".", ":(exclude).ues-work", ":(exclude).ues-learning", ":(exclude).ues-dashboard", ":(exclude).ues-sandboxes", ":(exclude).ues-cache", ":(exclude).ues-traces"],
|
|
260
|
-
["diff", "--binary", "--no-ext-diff", "--", ".", ":(exclude).ues-work", ":(exclude).ues-learning", ":(exclude).ues-dashboard", ":(exclude).ues-sandboxes", ":(exclude).ues-cache", ":(exclude).ues-traces"],
|
|
261
|
-
["diff", "--cached", "--binary", "--no-ext-diff", "--", ".", ":(exclude).ues-work", ":(exclude).ues-learning", ":(exclude).ues-dashboard", ":(exclude).ues-sandboxes", ":(exclude).ues-cache", ":(exclude).ues-traces"],
|
|
261
|
+
["status", "--porcelain=v1", "--untracked-files=all", "--", ".", ":(exclude).ues-work", ":(exclude).ues-learning", ":(exclude).ues-dashboard", ":(exclude).ues-sandboxes", ":(exclude).ues-cache", ":(exclude).ues-traces", ":(exclude).ues-memory"],
|
|
262
|
+
["diff", "--binary", "--no-ext-diff", "--", ".", ":(exclude).ues-work", ":(exclude).ues-learning", ":(exclude).ues-dashboard", ":(exclude).ues-sandboxes", ":(exclude).ues-cache", ":(exclude).ues-traces", ":(exclude).ues-memory"],
|
|
263
|
+
["diff", "--cached", "--binary", "--no-ext-diff", "--", ".", ":(exclude).ues-work", ":(exclude).ues-learning", ":(exclude).ues-dashboard", ":(exclude).ues-sandboxes", ":(exclude).ues-cache", ":(exclude).ues-traces", ":(exclude).ues-memory"],
|
|
262
264
|
]
|
|
263
265
|
for (const args of commands) {
|
|
264
266
|
const result = gitCapture(root, args)
|
|
@@ -1087,7 +1089,20 @@ export async function finalizeWork(root, slug, evidence) {
|
|
|
1087
1089
|
await writeJson(loaded.paths.evidence, loaded.evidence)
|
|
1088
1090
|
await writeJson(loaded.paths.state, loaded.state)
|
|
1089
1091
|
await journal(loaded.paths, "work.finalized", { fingerprint: currentFingerprint })
|
|
1090
|
-
|
|
1092
|
+
|
|
1093
|
+
const memory = await recordVerifiedTaskMemory(loaded.paths.root, {
|
|
1094
|
+
task: loaded.state.goal || loaded.plan.goal || `Completed durable work: ${slug}`,
|
|
1095
|
+
files: [...new Set(loaded.plan.tasks.flatMap((task) => taskFiles(task)))],
|
|
1096
|
+
verifier: verification.receipt?.verifier || "ues-integration-verifier",
|
|
1097
|
+
verifierOutput: [verification.evidence, verification.report].filter(Boolean).join("\n"),
|
|
1098
|
+
integrationOutput: evidenceText,
|
|
1099
|
+
sourceTask: `work:${slug}`,
|
|
1100
|
+
taskClass: "long-horizon",
|
|
1101
|
+
type: "episodic",
|
|
1102
|
+
scope: "project",
|
|
1103
|
+
}).catch(() => null)
|
|
1104
|
+
|
|
1105
|
+
return { state: loaded.state, evidence: loaded.evidence.entries.at(-1), memory }
|
|
1091
1106
|
})
|
|
1092
1107
|
}
|
|
1093
1108
|
|
|
@@ -1161,6 +1176,24 @@ async function buildContextPack(loaded, taskID) {
|
|
|
1161
1176
|
[task.title, task.summary, ...(task.acceptance || [])].join(" "),
|
|
1162
1177
|
).catch(() => [])
|
|
1163
1178
|
|
|
1179
|
+
const memoryRetrieval = await retrieveMemories(loaded.paths.root, taskText, {
|
|
1180
|
+
files: taskFiles(task),
|
|
1181
|
+
limit: 6,
|
|
1182
|
+
taskClass: contextPolicy.executionProfile || contextPolicy.profile?.name || contextPolicy.mode || null,
|
|
1183
|
+
touch: true,
|
|
1184
|
+
}).catch(() => ({ schemaVersion: 1, query: taskText, eligible: 0, results: [] }))
|
|
1185
|
+
const memories = memoryRetrieval.results || []
|
|
1186
|
+
const fabric = await capabilityFabricStatus(loaded.paths.root).catch(() => null)
|
|
1187
|
+
const providerHints = (fabric?.rows || [])
|
|
1188
|
+
.filter((row) => row.selected)
|
|
1189
|
+
.slice(0, 12)
|
|
1190
|
+
.map((row) => ({
|
|
1191
|
+
capability: row.capability,
|
|
1192
|
+
selected: row.selected,
|
|
1193
|
+
status: row.status,
|
|
1194
|
+
fallbacks: (row.fallbacks || []).slice(0, 3),
|
|
1195
|
+
}))
|
|
1196
|
+
|
|
1164
1197
|
const specStored = spec
|
|
1165
1198
|
? await putEvidence(loaded.paths.root, spec, {
|
|
1166
1199
|
kind: "work-spec",
|
|
@@ -1183,6 +1216,23 @@ async function buildContextPack(loaded, taskID) {
|
|
|
1183
1216
|
...(contextManifest?.evidencePointers || []).map((item) => item.ref),
|
|
1184
1217
|
...(contextManifest?.rankedReferences || []).slice(0, 12).map((item) => item.path),
|
|
1185
1218
|
],
|
|
1219
|
+
memories: memories.map((item) => ({
|
|
1220
|
+
id: item.id,
|
|
1221
|
+
type: item.type,
|
|
1222
|
+
scope: item.scope,
|
|
1223
|
+
content: item.content,
|
|
1224
|
+
confidence: item.confidence,
|
|
1225
|
+
files: item.files,
|
|
1226
|
+
retrieval: item.retrieval,
|
|
1227
|
+
})),
|
|
1228
|
+
contextHints: {
|
|
1229
|
+
hierarchy: (contextManifest?.hierarchy?.scopes || []).slice(0, 6).map((item) => ({
|
|
1230
|
+
path: item.path,
|
|
1231
|
+
score: item.score,
|
|
1232
|
+
l0: item.l0,
|
|
1233
|
+
})),
|
|
1234
|
+
providers: providerHints,
|
|
1235
|
+
},
|
|
1186
1236
|
recentFailure: recovery.stage === "initial" ? null : loaded.state.tasks?.[taskID]?.lastError || null,
|
|
1187
1237
|
nextAction: loaded.state.checkpoint?.nextAction || loaded.state.nextAction || null,
|
|
1188
1238
|
})
|
|
@@ -1220,6 +1270,17 @@ async function buildContextPack(loaded, taskID) {
|
|
|
1220
1270
|
planApproval: loaded.state.planApproval || null,
|
|
1221
1271
|
contextManifest,
|
|
1222
1272
|
acceptedLearnings: learnings,
|
|
1273
|
+
memories,
|
|
1274
|
+
memoryRetrieval: {
|
|
1275
|
+
schemaVersion: memoryRetrieval.schemaVersion || 1,
|
|
1276
|
+
eligible: memoryRetrieval.eligible || 0,
|
|
1277
|
+
returned: memories.length,
|
|
1278
|
+
},
|
|
1279
|
+
capabilityFabric: {
|
|
1280
|
+
healthyCapabilities: fabric?.healthyCapabilities || 0,
|
|
1281
|
+
totalCapabilities: fabric?.totalCapabilities || 0,
|
|
1282
|
+
providers: providerHints,
|
|
1283
|
+
},
|
|
1223
1284
|
workingState: {
|
|
1224
1285
|
status: loaded.state.status,
|
|
1225
1286
|
task: loaded.state.tasks?.[taskID],
|