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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +1557 -606
  2. package/bin/ocskill.mjs +172 -24
  3. package/docs/OPENCODE-COMPAT.md +34 -95
  4. package/docs/PI-COMPAT.md +188 -0
  5. package/docs/V14-CONTEXT-MEMORY-FABRIC.md +70 -0
  6. package/docs/V14.1-QUALITY-PERFORMANCE-FABRIC.md +114 -0
  7. package/docs/V14.2-TURBO-WEAK-MODEL-RUNTIME.md +448 -0
  8. package/evals/v14/tasks.json +46 -0
  9. package/global-config/agents/executor.md +7 -0
  10. package/global-config/agents/visual-verifier.md +22 -3
  11. package/global-config/commands/run.md +27 -20
  12. package/global-config/plugins/ues-router/command-runtime.js +54 -0
  13. package/global-config/plugins/ues-router/index.js +30 -24
  14. package/global-config/plugins/ues-router/policy-runtime.js +7 -0
  15. package/global-config/plugins/ues-router/router.js +12 -2
  16. package/global-config/skills/ecommerce-engineering/SKILL.md +1 -1
  17. package/global-config/skills/file-upload-engineering/SKILL.md +1 -1
  18. package/global-config/skills/git-safety/SKILL.md +1 -1
  19. package/global-config/skills/nestjs-engineering/SKILL.md +1 -1
  20. package/global-config/skills/performance-engineering/SKILL.md +1 -1
  21. package/global-config/skills/react-native-engineering/SKILL.md +1 -1
  22. package/global-config/skills/rest-api-design/SKILL.md +1 -1
  23. package/global-config/skills/ui-ux-engineering/SKILL.md +1 -1
  24. package/lib/adaptive-context-budget.mjs +97 -0
  25. package/lib/affected-tests.mjs +260 -0
  26. package/lib/benchmark-confidence.mjs +41 -2
  27. package/lib/browser-mcp-routing.mjs +166 -0
  28. package/lib/capability-fabric.mjs +336 -0
  29. package/lib/capability-registry.mjs +9 -0
  30. package/lib/context-engine-v11.mjs +65 -1
  31. package/lib/context-graph-rank.mjs +118 -0
  32. package/lib/context-manifest.mjs +97 -18
  33. package/lib/control-center.mjs +19 -1
  34. package/lib/dynamic-workflow.mjs +3 -1
  35. package/lib/evidence-store.mjs +82 -1
  36. package/lib/hierarchical-context.mjs +215 -0
  37. package/lib/memory-engine.mjs +465 -0
  38. package/lib/model-performance.mjs +33 -8
  39. package/lib/model-policy.mjs +3 -3
  40. package/lib/orchestrator-policy.mjs +5 -209
  41. package/lib/performance-fabric.mjs +229 -0
  42. package/lib/pi-rpc-pool.mjs +433 -0
  43. package/lib/process-hang-detector.mjs +83 -0
  44. package/lib/process-supervisor.mjs +193 -0
  45. package/lib/prompt-cache.mjs +2 -0
  46. package/lib/repo-graph.mjs +53 -2
  47. package/lib/runtime-config.mjs +31 -0
  48. package/lib/safety.mjs +132 -0
  49. package/lib/semantic-index.mjs +52 -3
  50. package/lib/skill-compiler.mjs +128 -0
  51. package/lib/skill-quality.mjs +48 -2
  52. package/lib/task-engine.mjs +66 -5
  53. package/lib/task-policy.mjs +235 -0
  54. package/lib/verification-broker.mjs +284 -0
  55. package/lib/verification-command.mjs +111 -0
  56. package/lib/windows-shim.mjs +35 -0
  57. package/lib/workspace-fingerprint.mjs +198 -0
  58. package/package.json +52 -42
  59. package/pi/extensions/ues-child-runtime.ts +238 -0
  60. package/pi/extensions/ues.ts +3200 -0
  61. package/pi/prompts/ues-audit.md +9 -0
  62. package/pi/prompts/ues-critique.md +9 -0
  63. package/pi/prompts/ues-debug.md +9 -0
  64. package/pi/prompts/ues-feature.md +9 -0
  65. package/pi/prompts/ues-fix.md +9 -0
  66. package/pi/prompts/ues-plan.md +9 -0
  67. package/pi/prompts/ues-research.md +9 -0
  68. package/pi/prompts/ues-resume.md +9 -0
  69. package/pi/prompts/ues-review.md +7 -0
  70. package/pi/prompts/ues-run.md +17 -0
  71. package/pi/prompts/ues-verify.md +9 -0
  72. package/scripts/check-release-consistency.mjs +119 -185
  73. package/scripts/check-runtime-exports.mjs +66 -0
  74. package/scripts/check-source-integrity.mjs +184 -0
  75. package/scripts/eval-pi.mjs +492 -0
  76. package/scripts/install.mjs +16 -0
  77. package/scripts/smoke-package-closure.mjs +110 -0
  78. package/scripts/smoke-packed-install.mjs +24 -11
  79. package/scripts/smoke-pi-extension.mjs +144 -0
  80. package/scripts/uninstall.mjs +44 -0
  81. package/CHANGELOG.md +0 -405
  82. package/docs/DETERMINISTIC-TOOLS.md +0 -105
  83. package/docs/ENGINEERING-DESIGN.md +0 -194
  84. package/docs/EVALS.md +0 -158
  85. package/docs/GITHUB-RULESET.md +0 -50
  86. package/docs/NPM-PUBLISH.md +0 -116
  87. package/docs/RESEARCH-SOURCES.md +0 -37
  88. package/docs/TRACE-SCHEMA.md +0 -122
  89. package/docs/V11-PERCEPTION-ADAPTIVE-EXECUTION.md +0 -75
  90. package/docs/V11-PERCEPTION-ADAPTIVE.md +0 -220
  91. package/docs/V12-WEAK-MODEL-INTELLIGENCE.md +0 -27
  92. package/docs/V13-PARALLEL-WEAK-MODEL-RUNTIME.md +0 -75
  93. package/docs/V7-INTELLIGENCE-RUNTIME.md +0 -166
  94. package/docs/V8-INTELLIGENCE-RELIABILITY.md +0 -206
  95. package/docs/V9-SPEED-INTELLIGENCE.md +0 -102
@@ -29,6 +29,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 || [],
@@ -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", "bin", "obj",
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
+ }
@@ -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", "bin", "obj",
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
+ }
@@ -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 = {
@@ -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
- return { state: loaded.state, evidence: loaded.evidence.entries.at(-1) }
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],