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
|
@@ -1,209 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function signal(name, matched, weight) {
|
|
8
|
-
return matched ? { name, weight } : null
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function profileFor(mode, risk) {
|
|
12
|
-
if (mode === "inline" && risk === "low") {
|
|
13
|
-
return {
|
|
14
|
-
name: "fast",
|
|
15
|
-
maxSkills: 2,
|
|
16
|
-
contextBudget: 8_000,
|
|
17
|
-
contextStrategy: "incremental-semantic",
|
|
18
|
-
skillLoading: "direct-only",
|
|
19
|
-
durableState: false,
|
|
20
|
-
worktree: "off",
|
|
21
|
-
critic: "off",
|
|
22
|
-
verification: "targeted",
|
|
23
|
-
fullCI: false,
|
|
24
|
-
containerVerification: "off",
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
if (mode === "long-horizon" || risk === "high") {
|
|
28
|
-
return {
|
|
29
|
-
name: "deep",
|
|
30
|
-
maxSkills: 5,
|
|
31
|
-
contextBudget: 48_000,
|
|
32
|
-
contextStrategy: "semantic+graph+git",
|
|
33
|
-
skillLoading: "orchestrated",
|
|
34
|
-
durableState: true,
|
|
35
|
-
worktree: "auto-writers",
|
|
36
|
-
critic: "required-for-high-risk-or-final",
|
|
37
|
-
verification: "targeted+integration",
|
|
38
|
-
fullCI: true,
|
|
39
|
-
containerVerification: risk === "high" ? "preferred-if-capable" : "optional",
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return {
|
|
43
|
-
name: "standard",
|
|
44
|
-
maxSkills: 4,
|
|
45
|
-
contextBudget: 20_000,
|
|
46
|
-
contextStrategy: "incremental-semantic+git",
|
|
47
|
-
skillLoading: "selective",
|
|
48
|
-
durableState: false,
|
|
49
|
-
worktree: "auto-on-conflict",
|
|
50
|
-
critic: "on-failure-or-elevated-risk",
|
|
51
|
-
verification: "targeted+affected",
|
|
52
|
-
fullCI: false,
|
|
53
|
-
containerVerification: "off",
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function boundedInt(value, fallback, min, max) {
|
|
58
|
-
const parsed = Number(value)
|
|
59
|
-
if (!Number.isFinite(parsed)) return fallback
|
|
60
|
-
return Math.min(max, Math.max(min, Math.round(parsed)))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function recoveryPolicyForAttempt(taskPolicy = {}, attempt = 1) {
|
|
64
|
-
const normalizedAttempt = boundedInt(attempt, 1, 1, 99)
|
|
65
|
-
const baseBudget = boundedInt(
|
|
66
|
-
taskPolicy.contextBudget ?? taskPolicy.profile?.contextBudget,
|
|
67
|
-
20_000,
|
|
68
|
-
4_000,
|
|
69
|
-
48_000,
|
|
70
|
-
)
|
|
71
|
-
const baseSkills = boundedInt(
|
|
72
|
-
taskPolicy.maxSkills ?? taskPolicy.profile?.maxSkills,
|
|
73
|
-
4,
|
|
74
|
-
1,
|
|
75
|
-
5,
|
|
76
|
-
)
|
|
77
|
-
const baseStrategy = taskPolicy.profile?.contextStrategy || "incremental-semantic+git"
|
|
78
|
-
|
|
79
|
-
if (normalizedAttempt <= 1) {
|
|
80
|
-
return {
|
|
81
|
-
schemaVersion: 1,
|
|
82
|
-
stage: "initial",
|
|
83
|
-
attempt: normalizedAttempt,
|
|
84
|
-
contextBudget: baseBudget,
|
|
85
|
-
maxSkills: baseSkills,
|
|
86
|
-
contextStrategy: baseStrategy,
|
|
87
|
-
requireDiagnosis: false,
|
|
88
|
-
requireCritic: false,
|
|
89
|
-
modelEscalation: false,
|
|
90
|
-
directives: [],
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (normalizedAttempt === 2) {
|
|
95
|
-
return {
|
|
96
|
-
schemaVersion: 1,
|
|
97
|
-
stage: "diagnose",
|
|
98
|
-
attempt: normalizedAttempt,
|
|
99
|
-
contextBudget: Math.min(48_000, Math.max(baseBudget, Math.round(baseBudget * 1.35))),
|
|
100
|
-
maxSkills: Math.min(5, baseSkills + 1),
|
|
101
|
-
contextStrategy:
|
|
102
|
-
taskPolicy.executionProfile === "fast"
|
|
103
|
-
? "incremental-semantic+git"
|
|
104
|
-
: baseStrategy,
|
|
105
|
-
requireDiagnosis: true,
|
|
106
|
-
requireCritic: false,
|
|
107
|
-
modelEscalation: true,
|
|
108
|
-
directives: [
|
|
109
|
-
"reproduce or capture the exact previous failure before editing",
|
|
110
|
-
"inspect the direct caller, nearest test and failure-adjacent evidence",
|
|
111
|
-
"do not stack another speculative patch on top of the failed attempt",
|
|
112
|
-
],
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
schemaVersion: 1,
|
|
118
|
-
stage: "deep-recovery",
|
|
119
|
-
attempt: normalizedAttempt,
|
|
120
|
-
contextBudget: Math.min(48_000, Math.max(20_000, Math.round(baseBudget * 1.75))),
|
|
121
|
-
maxSkills: Math.min(5, baseSkills + 2),
|
|
122
|
-
contextStrategy: "semantic+graph+git",
|
|
123
|
-
requireDiagnosis: true,
|
|
124
|
-
requireCritic: true,
|
|
125
|
-
modelEscalation: true,
|
|
126
|
-
directives: [
|
|
127
|
-
"re-investigate from fresh evidence and explicitly reject the failed hypothesis",
|
|
128
|
-
"expand to callers, dependencies, tests and boundary contracts before editing",
|
|
129
|
-
"challenge the architecture or coupling if repeated fixes expose a wider problem",
|
|
130
|
-
"run an independent critic or review pass before accepting the recovery",
|
|
131
|
-
],
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function classifyEngineeringTask(text, facts = {}) {
|
|
136
|
-
const value = String(text || "")
|
|
137
|
-
const declaredHighRisk =
|
|
138
|
-
["high", "critical"].includes(String(facts.risk || "").toLowerCase()) ||
|
|
139
|
-
/\b(?:risk|rủi ro)\s*[:=\/-]?\s*(?:high|critical|cao|nghiêm trọng)\b/i.test(value) ||
|
|
140
|
-
/\b(?:high|critical)[-\s]?(?:risk|rủi ro)\b/i.test(value)
|
|
141
|
-
const declaredLongHorizon =
|
|
142
|
-
facts.longHorizon === true ||
|
|
143
|
-
["long", "long-horizon", "deep"].includes(String(facts.mode || "").toLowerCase())
|
|
144
|
-
const compoundLongRisk =
|
|
145
|
-
/\blong\s*[/|,+]\s*(?:high|critical)[-\s]?risk\b/i.test(value) ||
|
|
146
|
-
/\b(?:high|critical)[-\s]?risk\s*[/|,+]\s*long\b/i.test(value)
|
|
147
|
-
const explicitLongHorizon = declaredLongHorizon || compoundLongRisk || LONG.test(value)
|
|
148
|
-
const signals = [
|
|
149
|
-
signal("long-request-text", value.length > 700, 1),
|
|
150
|
-
signal("medium-request-text", value.length > 250, 1),
|
|
151
|
-
signal("high-risk-domain", HIGH_RISK.test(value), 2),
|
|
152
|
-
signal("declared-high-risk", declaredHighRisk, 2),
|
|
153
|
-
signal("explicit-long-horizon", explicitLongHorizon, 2),
|
|
154
|
-
signal("debugging", DEBUG.test(value), 1),
|
|
155
|
-
signal("public-contract", CONTRACT.test(value) || facts.hasPublicContract, 2),
|
|
156
|
-
signal("data-migration", (DATA.test(value) && /migration|schema|migrate|di trú|chuyển đổi/i.test(value)) || facts.hasMigration, 2),
|
|
157
|
-
signal("many-changed-files", Number(facts.changedFiles || 0) > 5, 1),
|
|
158
|
-
signal("very-many-changed-files", Number(facts.changedFiles || 0) > 12, 1),
|
|
159
|
-
signal("large-repository", Number(facts.repoFiles || 0) > 1500, 1),
|
|
160
|
-
signal("monorepo", facts.monorepo === true, 1),
|
|
161
|
-
].filter(Boolean)
|
|
162
|
-
|
|
163
|
-
const score = signals.reduce((sum, item) => sum + item.weight, 0)
|
|
164
|
-
const highRisk = declaredHighRisk || HIGH_RISK.test(value) || Boolean(facts.hasMigration) || Boolean(facts.hasPublicContract)
|
|
165
|
-
const risk = highRisk ? "high" : score >= 3 ? "medium" : "low"
|
|
166
|
-
const mode = explicitLongHorizon || score >= 4 ? "long-horizon" : score >= 2 ? "standard" : "inline"
|
|
167
|
-
const modelTier = risk === "high" || mode === "long-horizon" ? "heavy" : score >= 2 ? "standard" : "light"
|
|
168
|
-
const maxAttempts = risk === "high" ? 2 : 3
|
|
169
|
-
const profile = profileFor(mode, risk)
|
|
170
|
-
|
|
171
|
-
const domains = []
|
|
172
|
-
if (/(auth|permission|tenant|token|session|phân quyền|xác thực)/i.test(value)) domains.push("auth-security")
|
|
173
|
-
if (/(payment|checkout|refund|webhook|thanh toán|hoàn tiền)/i.test(value)) domains.push("payment")
|
|
174
|
-
if (DATA.test(value)) domains.push("database")
|
|
175
|
-
if (CONTRACT.test(value)) domains.push("api-contract")
|
|
176
|
-
if (/(react native|expo|android|ios|gradle|xcode)/i.test(value)) domains.push("react-native")
|
|
177
|
-
else if (/(next\.js|nextjs|app router|server component)/i.test(value)) domains.push("nextjs")
|
|
178
|
-
else if (/\breact\b|useeffect|usestate|component/i.test(value)) domains.push("react")
|
|
179
|
-
if (/(docker|kubernetes|terraform|github actions|ci\/cd|deploy|triển khai)/i.test(value)) domains.push("devops")
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
schemaVersion: 4,
|
|
183
|
-
score,
|
|
184
|
-
signals,
|
|
185
|
-
risk,
|
|
186
|
-
mode,
|
|
187
|
-
executionProfile: profile.name,
|
|
188
|
-
modelTier,
|
|
189
|
-
maxAttempts,
|
|
190
|
-
contextBudget: profile.contextBudget,
|
|
191
|
-
maxSkills: profile.maxSkills,
|
|
192
|
-
requirePlanCheck: profile.durableState || risk === "high",
|
|
193
|
-
requireIntegrationVerification: mode !== "inline" || risk === "high",
|
|
194
|
-
requireFreshEvidence: true,
|
|
195
|
-
profile,
|
|
196
|
-
domains: [...new Set(domains)],
|
|
197
|
-
recovery: {
|
|
198
|
-
escalateAfterFailure: true,
|
|
199
|
-
diagnosisBeforePatch: true,
|
|
200
|
-
deepRecoveryFromAttempt: 3,
|
|
201
|
-
},
|
|
202
|
-
antiHallucination: {
|
|
203
|
-
evidenceFirst: true,
|
|
204
|
-
noCompletionWithoutVerification: mode !== "inline" || risk === "high",
|
|
205
|
-
noUnsupportedSemanticClaims: true,
|
|
206
|
-
failClosedOnMissingCapability: risk === "high",
|
|
207
|
-
},
|
|
208
|
-
}
|
|
209
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
classifyEngineeringTask,
|
|
3
|
+
recoveryPolicyForAttempt,
|
|
4
|
+
shouldRunDedicatedDiagnosis,
|
|
5
|
+
} from "./task-policy.mjs"
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { putEvidence } from "./evidence-store.mjs"
|
|
2
|
+
|
|
3
|
+
const DEFAULT_LIMIT = 64 * 1024
|
|
4
|
+
const SIGNAL_LINE = /\b(error|errors|failed|failure|fail|fatal|exception|warning|warn|assert|timeout|timed out|panic|traceback|mismatch|conflict|rejected|passed|pass|tests?|exit(?: code)?|changed)\b/i
|
|
5
|
+
|
|
6
|
+
function clampInt(value, fallback, min, max) {
|
|
7
|
+
const parsed = Number(value)
|
|
8
|
+
if (!Number.isFinite(parsed)) return fallback
|
|
9
|
+
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function uniqueSignalLines(text, limit = 48) {
|
|
13
|
+
const seen = new Set()
|
|
14
|
+
const rows = []
|
|
15
|
+
for (const raw of String(text || "").split(/\r?\n/)) {
|
|
16
|
+
const line = raw.trimEnd()
|
|
17
|
+
if (!line || !SIGNAL_LINE.test(line) || seen.has(line)) continue
|
|
18
|
+
seen.add(line)
|
|
19
|
+
rows.push(line)
|
|
20
|
+
if (rows.length >= limit) break
|
|
21
|
+
}
|
|
22
|
+
return rows
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function testSummaryLines(source, limit = 80) {
|
|
26
|
+
const rows = []
|
|
27
|
+
const seen = new Set()
|
|
28
|
+
const patterns = [
|
|
29
|
+
/^FAIL\b/i,
|
|
30
|
+
/^PASS\b/i,
|
|
31
|
+
/^Test Suites:/i,
|
|
32
|
+
/^Tests:/i,
|
|
33
|
+
/^Snapshots:/i,
|
|
34
|
+
/^Time:/i,
|
|
35
|
+
/^Ran all test suites/i,
|
|
36
|
+
/Jest did not exit/i,
|
|
37
|
+
/Exceeded timeout/i,
|
|
38
|
+
/^\s*●\s+/,
|
|
39
|
+
/^\s*at\s+.+:\d+:\d+/,
|
|
40
|
+
/(?:AssertionError|Expected|Received|Traceback|panic:|FAILED\s)/i,
|
|
41
|
+
]
|
|
42
|
+
for (const raw of String(source || "").split(/\r?\n/)) {
|
|
43
|
+
const line = raw.trimEnd()
|
|
44
|
+
if (!line || seen.has(line) || !patterns.some((re) => re.test(line))) continue
|
|
45
|
+
seen.add(line)
|
|
46
|
+
rows.push(line)
|
|
47
|
+
if (rows.length >= limit) break
|
|
48
|
+
}
|
|
49
|
+
return rows
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function gitSummaryLines(source, limit = 120) {
|
|
53
|
+
const rows = []
|
|
54
|
+
for (const raw of String(source || "").split(/\r?\n/)) {
|
|
55
|
+
if (
|
|
56
|
+
/^diff --git /.test(raw) ||
|
|
57
|
+
/^@@ /.test(raw) ||
|
|
58
|
+
/^(?:---|\+\+\+) /.test(raw) ||
|
|
59
|
+
/^fatal:/i.test(raw) ||
|
|
60
|
+
/^error:/i.test(raw) ||
|
|
61
|
+
/^CONFLICT\b/i.test(raw)
|
|
62
|
+
) {
|
|
63
|
+
rows.push(raw)
|
|
64
|
+
if (rows.length >= limit) break
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return rows
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
function lintSummaryLines(source, limit = 100) {
|
|
72
|
+
const rows = []
|
|
73
|
+
const seen = new Set()
|
|
74
|
+
const patterns = [
|
|
75
|
+
/\b(?:error|warning)\b/i,
|
|
76
|
+
/\b\d+\s+problems?\b/i,
|
|
77
|
+
/\b\d+\s+errors?\b/i,
|
|
78
|
+
/\b\d+\s+warnings?\b/i,
|
|
79
|
+
/\b(?:eslint|ruff|flake8|pylint|clippy)\b/i,
|
|
80
|
+
/:\d+:\d+\b/,
|
|
81
|
+
/^✖\s+/,
|
|
82
|
+
]
|
|
83
|
+
for (const raw of String(source || "").split(/\r?\n/)) {
|
|
84
|
+
const line = raw.trimEnd()
|
|
85
|
+
if (!line || seen.has(line) || !patterns.some((pattern) => pattern.test(line))) continue
|
|
86
|
+
seen.add(line)
|
|
87
|
+
rows.push(line)
|
|
88
|
+
if (rows.length >= limit) break
|
|
89
|
+
}
|
|
90
|
+
return rows
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildSummaryLines(source, limit = 100) {
|
|
94
|
+
const rows = []
|
|
95
|
+
const seen = new Set()
|
|
96
|
+
const patterns = [
|
|
97
|
+
/\berror\s+TS\d+\b/i,
|
|
98
|
+
/\b(?:compile|compiled|compilation|build)\b.*\b(?:failed|success|succeeded|complete|completed)\b/i,
|
|
99
|
+
/\b(?:failed|fatal|exception|panic)\b/i,
|
|
100
|
+
/\bwarning\b/i,
|
|
101
|
+
/:\d+:\d+\b/,
|
|
102
|
+
/\b(?:ELIFECYCLE|ERR_|exit code)\b/i,
|
|
103
|
+
]
|
|
104
|
+
for (const raw of String(source || "").split(/\r?\n/)) {
|
|
105
|
+
const line = raw.trimEnd()
|
|
106
|
+
if (!line || seen.has(line) || !patterns.some((pattern) => pattern.test(line))) continue
|
|
107
|
+
seen.add(line)
|
|
108
|
+
rows.push(line)
|
|
109
|
+
if (rows.length >= limit) break
|
|
110
|
+
}
|
|
111
|
+
return rows
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function jsonSummaryLines(source, limit = 80) {
|
|
115
|
+
const text = String(source || "").trim()
|
|
116
|
+
if (!text || text.length > 4 * 1024 * 1024 || !/^[\[{]/.test(text)) return []
|
|
117
|
+
let value
|
|
118
|
+
try { value = JSON.parse(text) } catch { return [] }
|
|
119
|
+
|
|
120
|
+
const rows = []
|
|
121
|
+
const visit = (current, prefix, depth) => {
|
|
122
|
+
if (rows.length >= limit || depth > 3) return
|
|
123
|
+
if (Array.isArray(current)) {
|
|
124
|
+
rows.push(`${prefix || "$"}: array(${current.length})`)
|
|
125
|
+
current.slice(0, 8).forEach((item, index) => visit(item, `${prefix || "$"}[${index}]`, depth + 1))
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
if (current && typeof current === "object") {
|
|
129
|
+
const keys = Object.keys(current)
|
|
130
|
+
rows.push(`${prefix || "$"}: object(${keys.length}) keys=[${keys.slice(0, 16).join(", ")}]`)
|
|
131
|
+
for (const key of keys.slice(0, 12)) {
|
|
132
|
+
visit(current[key], prefix ? `${prefix}.${key}` : key, depth + 1)
|
|
133
|
+
if (rows.length >= limit) break
|
|
134
|
+
}
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
const rendered = JSON.stringify(current)
|
|
138
|
+
rows.push(`${prefix || "$"}: ${String(rendered).slice(0, 160)}`)
|
|
139
|
+
}
|
|
140
|
+
visit(value, "", 0)
|
|
141
|
+
return rows
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function structuredSignalLines(source, options = {}) {
|
|
145
|
+
const hint = String(options.kind || options.source || options.command || "").toLowerCase()
|
|
146
|
+
if (/(jest|vitest|pytest|test|spec)/.test(hint)) {
|
|
147
|
+
const rows = testSummaryLines(source)
|
|
148
|
+
if (rows.length) return { kind: "test", rows }
|
|
149
|
+
}
|
|
150
|
+
if (/(eslint|lint|ruff|flake8|pylint|clippy)/.test(hint)) {
|
|
151
|
+
const rows = lintSummaryLines(source)
|
|
152
|
+
if (rows.length) return { kind: "lint", rows }
|
|
153
|
+
}
|
|
154
|
+
if (/(typecheck|tsc|compile|build|gradle|maven|cargo check|dotnet build)/.test(hint)) {
|
|
155
|
+
const rows = buildSummaryLines(source)
|
|
156
|
+
if (rows.length) return { kind: "build", rows }
|
|
157
|
+
}
|
|
158
|
+
if (/(git|diff|status)/.test(hint)) {
|
|
159
|
+
const rows = gitSummaryLines(source)
|
|
160
|
+
if (rows.length) return { kind: "git", rows }
|
|
161
|
+
}
|
|
162
|
+
if (/(json|--json)/.test(hint) || /^[\s]*[\[{]/.test(String(source || ""))) {
|
|
163
|
+
const rows = jsonSummaryLines(source)
|
|
164
|
+
if (rows.length) return { kind: "json", rows }
|
|
165
|
+
}
|
|
166
|
+
const rows = uniqueSignalLines(source)
|
|
167
|
+
return { kind: rows.length ? "signal" : "generic", rows }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function boundedPreview(text, maxChars, options = {}) {
|
|
171
|
+
const source = String(text || "")
|
|
172
|
+
const headBudget = Math.max(2048, Math.floor(maxChars * 0.24))
|
|
173
|
+
const tailBudget = Math.max(4096, Math.floor(maxChars * 0.30))
|
|
174
|
+
const signalBudget = Math.max(2048, maxChars - headBudget - tailBudget - 1800)
|
|
175
|
+
const head = source.slice(0, headBudget)
|
|
176
|
+
const tail = source.slice(Math.max(head.length, source.length - tailBudget))
|
|
177
|
+
const structured = structuredSignalLines(source, options)
|
|
178
|
+
let signalText = structured.rows.join("\n")
|
|
179
|
+
if (signalText.length > signalBudget) signalText = signalText.slice(0, signalBudget) + "\n...[signal lines truncated]"
|
|
180
|
+
return { head, tail, signalText, signalKind: structured.kind }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function compactReversibleOutput(root, text, options = {}) {
|
|
184
|
+
const source = String(text ?? "")
|
|
185
|
+
const maxChars = clampInt(options.maxChars, DEFAULT_LIMIT, 8 * 1024, 256 * 1024)
|
|
186
|
+
if (source.length <= maxChars) {
|
|
187
|
+
return {
|
|
188
|
+
schemaVersion: 1,
|
|
189
|
+
compacted: false,
|
|
190
|
+
strategy: "raw",
|
|
191
|
+
originalChars: source.length,
|
|
192
|
+
returnedChars: source.length,
|
|
193
|
+
evidenceRef: null,
|
|
194
|
+
text: source,
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const evidence = await putEvidence(root, source, {
|
|
199
|
+
kind: options.kind || "raw-tool-output",
|
|
200
|
+
source: options.source || "ues-performance-fabric",
|
|
201
|
+
summary: options.summary || `Raw output preserved before model-visible compaction (${source.length} chars)`,
|
|
202
|
+
})
|
|
203
|
+
const { head, tail, signalText, signalKind } = boundedPreview(source, maxChars, options)
|
|
204
|
+
const recovery = `Raw captured output: ${evidence.ref}. Recover exact captured bytes in slices with: ues store get ${evidence.ref} --start N --max 24000`
|
|
205
|
+
let preview = [
|
|
206
|
+
`[UES reversible output compaction: ${source.length} -> <=${maxChars} chars; reducer=${signalKind}]`,
|
|
207
|
+
recovery,
|
|
208
|
+
"",
|
|
209
|
+
"--- head ---",
|
|
210
|
+
head,
|
|
211
|
+
signalText ? "\n--- high-signal lines ---\n" + signalText : "",
|
|
212
|
+
"\n--- tail ---",
|
|
213
|
+
tail,
|
|
214
|
+
].filter(Boolean).join("\n")
|
|
215
|
+
if (preview.length > maxChars) {
|
|
216
|
+
const reserve = Math.min(1400, recovery.length + 160)
|
|
217
|
+
preview = preview.slice(0, Math.max(0, maxChars - reserve)) + `\n...[model-visible preview truncated]\n${recovery}`
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
schemaVersion: 1,
|
|
222
|
+
compacted: true,
|
|
223
|
+
strategy: `reversible-head-${signalKind}-tail`,
|
|
224
|
+
originalChars: source.length,
|
|
225
|
+
returnedChars: preview.length,
|
|
226
|
+
evidenceRef: evidence.ref,
|
|
227
|
+
text: preview,
|
|
228
|
+
}
|
|
229
|
+
}
|