opencode-agent-skill 15.0.0 → 15.1.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 +5 -5
- package/docs/V15-MANAGED-RUNTIME.md +18 -0
- package/global-config/agents/verifier.md +6 -2
- package/lib/affected-tests.mjs +58 -8
- package/lib/capability-fabric.mjs +107 -48
- package/lib/code-intelligence/index.mjs +4 -15
- package/lib/code-intelligence/lsp-pool.mjs +694 -0
- package/lib/code-intelligence/lsp-provider.mjs +481 -67
- package/lib/compaction-resume-guard.mjs +327 -0
- package/lib/completion-auditor.mjs +20 -0
- package/lib/context-engine-v11.mjs +20 -7
- package/lib/context-manifest.mjs +38 -21
- package/lib/decision-policy.mjs +40 -2
- package/lib/evidence-store.mjs +121 -31
- package/lib/executable-probe.mjs +69 -0
- package/lib/execution-contract.mjs +29 -16
- package/lib/hierarchical-context.mjs +31 -3
- package/lib/learning-engine.mjs +164 -65
- package/lib/memory-engine.mjs +348 -98
- package/lib/model-config.mjs +86 -7
- package/lib/performance-fabric.mjs +354 -107
- package/lib/permission-policy.mjs +232 -0
- package/lib/pi-rpc-pool.mjs +170 -5
- package/lib/provider-recovery.mjs +51 -0
- package/lib/repo-graph.mjs +38 -5
- package/lib/runtime-events.mjs +31 -10
- package/lib/semantic-index.mjs +67 -20
- package/lib/skill-compiler.mjs +84 -18
- package/lib/subagent-artifacts.mjs +182 -0
- package/lib/task-policy.mjs +80 -7
- package/lib/trajectory.mjs +25 -1
- package/lib/untrusted-output.mjs +106 -0
- package/lib/verification-broker.mjs +32 -27
- package/lib/workspace-fingerprint.mjs +43 -8
- package/lib/workspace-hygiene.mjs +481 -0
- package/package.json +8 -6
- package/pi/extensions/ues-child-runtime.ts +103 -5
- package/pi/extensions/ues.ts +984 -95
- package/scripts/benchmark-lsp-pool.mjs +128 -0
- package/scripts/check-release-consistency.mjs +16 -4
- package/scripts/check-source-integrity.mjs +60 -1
- package/scripts/run-test-suite.mjs +143 -0
- package/scripts/smoke-package-closure.mjs +4 -0
|
@@ -9,23 +9,41 @@ function clampInt(value, fallback, min, max) {
|
|
|
9
9
|
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function
|
|
12
|
+
function hintFor(options = {}) {
|
|
13
|
+
return String(options.command || options.source || options.kind || "")
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/\s+/g, " ")
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function uniqRows(rows, limit = 100) {
|
|
13
20
|
const seen = new Set()
|
|
21
|
+
const result = []
|
|
22
|
+
for (const value of rows || []) {
|
|
23
|
+
const row = String(value || "").trimEnd()
|
|
24
|
+
if (!row || seen.has(row)) continue
|
|
25
|
+
seen.add(row)
|
|
26
|
+
result.push(row)
|
|
27
|
+
if (result.length >= limit) break
|
|
28
|
+
}
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function matchingLines(source, patterns, limit = 100) {
|
|
14
33
|
const rows = []
|
|
15
|
-
for (const raw of String(
|
|
16
|
-
|
|
17
|
-
if (!line || !SIGNAL_LINE.test(line) || seen.has(line)) continue
|
|
18
|
-
seen.add(line)
|
|
19
|
-
rows.push(line)
|
|
34
|
+
for (const raw of String(source || "").split(/\r?\n/)) {
|
|
35
|
+
if (patterns.some((pattern) => pattern.test(raw))) rows.push(raw)
|
|
20
36
|
if (rows.length >= limit) break
|
|
21
37
|
}
|
|
22
|
-
return rows
|
|
38
|
+
return uniqRows(rows, limit)
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
function uniqueSignalLines(text, limit = 48) {
|
|
42
|
+
return matchingLines(text, [SIGNAL_LINE], limit)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function npmTestRows(source, limit = 100) {
|
|
46
|
+
return matchingLines(source, [
|
|
29
47
|
/^FAIL\b/i,
|
|
30
48
|
/^PASS\b/i,
|
|
31
49
|
/^Test Suites:/i,
|
|
@@ -38,80 +56,191 @@ function testSummaryLines(source, limit = 80) {
|
|
|
38
56
|
/^\s*●\s+/,
|
|
39
57
|
/^\s*at\s+.+:\d+:\d+/,
|
|
40
58
|
/(?:AssertionError|Expected|Received|Traceback|panic:|FAILED\s)/i,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
seen.add(line)
|
|
46
|
-
rows.push(line)
|
|
47
|
-
if (rows.length >= limit) break
|
|
48
|
-
}
|
|
49
|
-
return rows
|
|
59
|
+
/^not ok\b/i,
|
|
60
|
+
/^ok\s+\d+\b/i,
|
|
61
|
+
/^#\s+(?:tests|suites|pass|fail|cancelled|skipped|todo|duration_ms)\b/i,
|
|
62
|
+
], limit)
|
|
50
63
|
}
|
|
51
64
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
function pytestRows(source, limit = 100) {
|
|
66
|
+
return matchingLines(source, [
|
|
67
|
+
/^(?:FAILED|ERROR)\b/,
|
|
68
|
+
/^=+\s+.*(?:passed|failed|error|skipped|xfailed|xpassed).*\s+=+$/i,
|
|
69
|
+
/^E\s{2,}/,
|
|
70
|
+
/^_+\s+.+\s+_+$/,
|
|
71
|
+
/^Traceback \(most recent call last\):/,
|
|
72
|
+
/^\s*File ".+", line \d+/,
|
|
73
|
+
/(?:AssertionError|ImportError|ModuleNotFoundError|pytest\.)/,
|
|
74
|
+
/collected\s+\d+\s+items?/i,
|
|
75
|
+
], limit)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function gitDiffRows(source, limit = 140) {
|
|
79
|
+
return matchingLines(source, [
|
|
80
|
+
/^diff --git /,
|
|
81
|
+
/^index [0-9a-f.]+/i,
|
|
82
|
+
/^@@ /,
|
|
83
|
+
/^(?:---|\+\+\+) /,
|
|
84
|
+
/^fatal:/i,
|
|
85
|
+
/^error:/i,
|
|
86
|
+
/^CONFLICT\b/i,
|
|
87
|
+
/^rename (?:from|to) /,
|
|
88
|
+
/^new file mode /,
|
|
89
|
+
/^deleted file mode /,
|
|
90
|
+
/^\s*\d+ files? changed/i,
|
|
91
|
+
], limit)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function gitStatusRows(source, limit = 120) {
|
|
95
|
+
return matchingLines(source, [
|
|
96
|
+
/^##\s+/,
|
|
97
|
+
/^On branch\s+/i,
|
|
98
|
+
/^Your branch\s+/i,
|
|
99
|
+
/^Changes to be committed:/i,
|
|
100
|
+
/^Changes not staged for commit:/i,
|
|
101
|
+
/^Untracked files:/i,
|
|
102
|
+
/^Unmerged paths:/i,
|
|
103
|
+
/^\s*(?:nothing to commit|working tree clean)/i,
|
|
104
|
+
/^[ MARCUD?!]{1,2}\s+\S+/,
|
|
105
|
+
], limit)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function gitLogRows(source, limit = 100) {
|
|
109
|
+
return matchingLines(source, [
|
|
110
|
+
/^commit\s+[0-9a-f]{7,40}\b/i,
|
|
111
|
+
/^Author:\s+/i,
|
|
112
|
+
/^Date:\s+/i,
|
|
113
|
+
/^Merge:\s+/i,
|
|
114
|
+
/^\s{4}\S+/,
|
|
115
|
+
/^fatal:/i,
|
|
116
|
+
/^error:/i,
|
|
117
|
+
], limit)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function verificationTestRows(source, limit = 120) {
|
|
121
|
+
return matchingLines(source, [
|
|
122
|
+
/^(?:ok|FAIL)\s+\S+/,
|
|
123
|
+
/^---\s+(?:PASS|FAIL):\s+/,
|
|
124
|
+
/^test result:\s+(?:ok|FAILED)\b/i,
|
|
125
|
+
/^failures?:/i,
|
|
126
|
+
/^running\s+\d+\s+tests?/i,
|
|
127
|
+
/^Finished\s+test\b/i,
|
|
128
|
+
/^Total tests:\s*\d+/i,
|
|
129
|
+
/^\s*(?:Passed|Failed|Skipped):\s*\d+/i,
|
|
130
|
+
/\b(?:panic|assertion failed|FAILED|FAILURE|ERROR)\b/i,
|
|
131
|
+
/\berror\[E\d+\]/,
|
|
132
|
+
/\bBUILD\s+(?:SUCCESS|FAILURE)\b/i,
|
|
133
|
+
/\bTests run:\s*\d+/i,
|
|
134
|
+
], limit)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function pythonLintRows(source, limit = 120) {
|
|
138
|
+
return matchingLines(source, [
|
|
139
|
+
/^.+:\d+:\d+:\s+[A-Z]+\d+\s+/,
|
|
140
|
+
/^.+:\d+:\d+:\s+(?:error|warning)\b/i,
|
|
141
|
+
/^Found\s+\d+\s+errors?/i,
|
|
142
|
+
/^All checks passed!?$/i,
|
|
143
|
+
/^\d+\s+files?\s+(?:reformatted|left unchanged)/i,
|
|
144
|
+
/\b(?:error|warning|failed)\b/i,
|
|
145
|
+
], limit)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function packageInstallRows(source, limit = 120) {
|
|
149
|
+
return matchingLines(source, [
|
|
150
|
+
/\b(?:added|removed|changed)\s+\d+\s+packages?\b/i,
|
|
151
|
+
/\baudited\s+\d+\s+packages?\b/i,
|
|
152
|
+
/\b\d+\s+packages?\s+(?:are\s+)?looking for funding\b/i,
|
|
153
|
+
/\b\d+\s+vulnerabilit(?:y|ies)\b/i,
|
|
154
|
+
/\b(?:deprecated|warning|warn|error|ERR!|ELIFECYCLE|ERR_)\b/i,
|
|
155
|
+
/^Packages:\s+[+-]?\d+/i,
|
|
156
|
+
/^Progress:\s+/i,
|
|
157
|
+
/^Done in\s+/i,
|
|
158
|
+
], limit)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function rgRows(source, limit = 100) {
|
|
162
|
+
const lines = String(source || "").split(/\r?\n/).filter(Boolean)
|
|
163
|
+
const matched = []
|
|
164
|
+
for (const line of lines) {
|
|
55
165
|
if (
|
|
56
|
-
/^
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/^CONFLICT\b/i.test(raw)
|
|
62
|
-
) {
|
|
63
|
-
rows.push(raw)
|
|
64
|
-
if (rows.length >= limit) break
|
|
65
|
-
}
|
|
166
|
+
/^(?:[^:\n]+:)?\d+(?::\d+)?:/.test(line) ||
|
|
167
|
+
/^\.?[^:\n]+:\s/.test(line) ||
|
|
168
|
+
/(?:error|permission denied|binary file|no such file)/i.test(line)
|
|
169
|
+
) matched.push(line)
|
|
170
|
+
if (matched.length >= limit) break
|
|
66
171
|
}
|
|
67
|
-
|
|
172
|
+
const rows = matched.length ? matched : lines.slice(0, limit)
|
|
173
|
+
return uniqRows(rows, limit)
|
|
68
174
|
}
|
|
69
175
|
|
|
176
|
+
function treeRows(source, limit = 120) {
|
|
177
|
+
const lines = String(source || "").split(/\r?\n/).filter(Boolean)
|
|
178
|
+
if (lines.length <= limit) return uniqRows(lines, limit)
|
|
179
|
+
const head = lines.slice(0, Math.max(1, limit - 12))
|
|
180
|
+
const tail = lines.slice(-12)
|
|
181
|
+
return uniqRows([...head, `...[tree omitted ${Math.max(0, lines.length - head.length - tail.length)} rows]...`, ...tail], limit)
|
|
182
|
+
}
|
|
70
183
|
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
184
|
+
function tscRows(source, limit = 120) {
|
|
185
|
+
return matchingLines(source, [
|
|
186
|
+
/\berror\s+TS\d+\b/i,
|
|
187
|
+
/^.+\(\d+,\d+\):\s+error\s+TS\d+/i,
|
|
188
|
+
/^.+:\d+:\d+\s+-\s+error\s+TS\d+/i,
|
|
189
|
+
/^Found\s+\d+\s+errors?/i,
|
|
190
|
+
/\b(?:ELIFECYCLE|ERR_|exit code)\b/i,
|
|
191
|
+
], limit)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function eslintRows(source, limit = 120) {
|
|
195
|
+
return matchingLines(source, [
|
|
196
|
+
/^\/?[^\n]+\.[cm]?[jt]sx?$/i,
|
|
197
|
+
/^\s*\d+:\d+\s+(?:error|warning)\s+/i,
|
|
76
198
|
/\b\d+\s+problems?\b/i,
|
|
77
199
|
/\b\d+\s+errors?\b/i,
|
|
78
200
|
/\b\d+\s+warnings?\b/i,
|
|
79
|
-
/\b(?:eslint|ruff|flake8|pylint|clippy)\b/i,
|
|
80
|
-
/:\d+:\d+\b/,
|
|
81
201
|
/^✖\s+/,
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
202
|
+
/\beslint\b.*\b(?:error|warning|failed)\b/i,
|
|
203
|
+
], limit)
|
|
91
204
|
}
|
|
92
205
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/\
|
|
206
|
+
function dockerRows(source, limit = 120) {
|
|
207
|
+
return matchingLines(source, [
|
|
208
|
+
/^#\d+\s+/,
|
|
209
|
+
/^\s*=>\s+/,
|
|
210
|
+
/\b(?:error|failed|failure|fatal|warning|unhealthy|exited)\b/i,
|
|
211
|
+
/^NAME\s+IMAGE\s+COMMAND/i,
|
|
212
|
+
/^NAME\s+STATUS/i,
|
|
213
|
+
/^\[?\+\]?\s+(?:Running|Started|Stopped|Created|Built|Recreated)/i,
|
|
214
|
+
/(?:container|service|network|volume).*(?:created|started|stopped|failed|error)/i,
|
|
215
|
+
], limit)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function prismaRows(source, limit = 120) {
|
|
219
|
+
return matchingLines(source, [
|
|
220
|
+
/Prisma schema loaded from/i,
|
|
221
|
+
/Datasource\s+".+"/i,
|
|
222
|
+
/Applying migration/i,
|
|
223
|
+
/migrations? found/i,
|
|
224
|
+
/Generated Prisma Client/i,
|
|
225
|
+
/Your database is now in sync/i,
|
|
226
|
+
/The following migration/i,
|
|
227
|
+
/\bP\d{4}\b/,
|
|
228
|
+
/\b(?:error|failed|warning)\b/i,
|
|
229
|
+
/schema\.prisma:\d+/i,
|
|
230
|
+
], limit)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function genericBuildRows(source, limit = 100) {
|
|
234
|
+
return matchingLines(source, [
|
|
98
235
|
/\b(?:compile|compiled|compilation|build)\b.*\b(?:failed|success|succeeded|complete|completed)\b/i,
|
|
99
236
|
/\b(?:failed|fatal|exception|panic)\b/i,
|
|
100
237
|
/\bwarning\b/i,
|
|
101
238
|
/:\d+:\d+\b/,
|
|
102
239
|
/\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
|
|
240
|
+
], limit)
|
|
112
241
|
}
|
|
113
242
|
|
|
114
|
-
function
|
|
243
|
+
function jsonRows(source, limit = 80) {
|
|
115
244
|
const text = String(source || "").trim()
|
|
116
245
|
if (!text || text.length > 4 * 1024 * 1024 || !/^[\[{]/.test(text)) return []
|
|
117
246
|
let value
|
|
@@ -134,50 +263,157 @@ function jsonSummaryLines(source, limit = 80) {
|
|
|
134
263
|
}
|
|
135
264
|
return
|
|
136
265
|
}
|
|
137
|
-
|
|
138
|
-
rows.push(`${prefix || "$"}: ${String(rendered).slice(0, 160)}`)
|
|
266
|
+
rows.push(`${prefix || "$"}: ${String(JSON.stringify(current)).slice(0, 160)}`)
|
|
139
267
|
}
|
|
140
268
|
visit(value, "", 0)
|
|
141
269
|
return rows
|
|
142
270
|
}
|
|
143
271
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
272
|
+
const REDUCERS = Object.freeze([
|
|
273
|
+
{
|
|
274
|
+
family: "npm-test",
|
|
275
|
+
match: (hint) => /(?:^|\s)(?:npm|pnpm|yarn)\s+(?:run\s+)?test\b|node\s+--test\b|\b(?:jest|vitest)\b/.test(hint),
|
|
276
|
+
rows: npmTestRows,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
family: "pytest",
|
|
280
|
+
match: (hint) => /\b(?:pytest|py\.test)\b/.test(hint),
|
|
281
|
+
rows: pytestRows,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
family: "git-status",
|
|
285
|
+
match: (hint) => /\bgit\s+status\b/.test(hint),
|
|
286
|
+
rows: gitStatusRows,
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
family: "git-log",
|
|
290
|
+
match: (hint) => /\bgit\s+log\b/.test(hint),
|
|
291
|
+
rows: gitLogRows,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
family: "git-diff",
|
|
295
|
+
match: (hint) => /\bgit\s+(?:diff|show)\b/.test(hint),
|
|
296
|
+
rows: gitDiffRows,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
family: "ripgrep",
|
|
300
|
+
match: (hint) => /(?:^|\s)(?:rg|ripgrep)(?:\s|$)|\bgrep\b/.test(hint),
|
|
301
|
+
rows: rgRows,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
family: "tree",
|
|
305
|
+
match: (hint) => /(?:^|[;&|]\s*|\s)(?:tree)(?:\s|$)/.test(hint) || hint === "tree",
|
|
306
|
+
rows: treeRows,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
family: "tsc",
|
|
310
|
+
match: (hint) => /\b(?:tsc|typescript|typecheck)\b/.test(hint),
|
|
311
|
+
rows: tscRows,
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
family: "eslint",
|
|
315
|
+
match: (hint) => /\b(?:eslint|lint)\b/.test(hint),
|
|
316
|
+
rows: eslintRows,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
family: "docker",
|
|
320
|
+
match: (hint) => /\bdocker(?:\s+compose)?\b/.test(hint),
|
|
321
|
+
rows: dockerRows,
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
family: "prisma",
|
|
325
|
+
match: (hint) => /\bprisma\b|schema\.prisma/.test(hint),
|
|
326
|
+
rows: prismaRows,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
family: "package-install",
|
|
330
|
+
match: (hint) => /(?:^|\s)(?:npm\s+(?:i|install|ci)|pnpm\s+(?:i|install|add)|yarn\s+(?:install|add))(?:\s|$)/.test(hint),
|
|
331
|
+
rows: packageInstallRows,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
family: "verification-test",
|
|
335
|
+
match: (hint) => /\b(?:go\s+test|cargo\s+test|dotnet\s+test|mvn\s+test|gradle\s+test)\b/.test(hint),
|
|
336
|
+
rows: verificationTestRows,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
family: "python-lint",
|
|
340
|
+
match: (hint) => /\b(?:ruff|flake8|pylint)\b/.test(hint),
|
|
341
|
+
rows: pythonLintRows,
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
family: "build",
|
|
345
|
+
match: (hint) => /\b(?:compile|build|gradle|maven|mvn|cargo\s+check|dotnet\s+build)\b/.test(hint),
|
|
346
|
+
rows: genericBuildRows,
|
|
347
|
+
},
|
|
348
|
+
])
|
|
349
|
+
|
|
350
|
+
export const COMMAND_REDUCER_FAMILIES = Object.freeze(REDUCERS.map((item) => item.family))
|
|
351
|
+
|
|
352
|
+
export function reduceCommandOutput(source, options = {}) {
|
|
353
|
+
const text = String(source || "")
|
|
354
|
+
const hint = hintFor(options)
|
|
355
|
+
const reducer = REDUCERS.find((item) => item.match(hint))
|
|
356
|
+
if (reducer) {
|
|
357
|
+
const rows = reducer.rows(text)
|
|
358
|
+
if (rows.length) {
|
|
359
|
+
return {
|
|
360
|
+
schemaVersion: 2,
|
|
361
|
+
family: reducer.family,
|
|
362
|
+
hint,
|
|
363
|
+
rows,
|
|
364
|
+
summary: `command-aware reducer=${reducer.family}; selected=${rows.length}; totalLines=${text.split(/\r?\n/).length}`,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
157
367
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
368
|
+
|
|
369
|
+
if (/(?:--json|\bjson\b)/.test(hint) || /^[\s]*[\[{]/.test(text)) {
|
|
370
|
+
const rows = jsonRows(text)
|
|
371
|
+
if (rows.length) {
|
|
372
|
+
return {
|
|
373
|
+
schemaVersion: 2,
|
|
374
|
+
family: "json",
|
|
375
|
+
hint,
|
|
376
|
+
rows,
|
|
377
|
+
summary: `command-aware reducer=json; selected=${rows.length}`,
|
|
378
|
+
}
|
|
379
|
+
}
|
|
161
380
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
381
|
+
|
|
382
|
+
const rows = uniqueSignalLines(text)
|
|
383
|
+
return {
|
|
384
|
+
schemaVersion: 2,
|
|
385
|
+
family: rows.length ? "signal" : "generic",
|
|
386
|
+
hint,
|
|
387
|
+
rows,
|
|
388
|
+
summary: rows.length
|
|
389
|
+
? `generic signal reducer; selected=${rows.length}`
|
|
390
|
+
: "generic head/tail fallback; no structured signal reducer matched",
|
|
165
391
|
}
|
|
166
|
-
|
|
167
|
-
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function legacyStrategyKind(source, options = {}) {
|
|
395
|
+
const hint = hintFor(options)
|
|
396
|
+
if (/(jest|vitest|pytest|test|spec)/.test(hint) && npmTestRows(source).length) return "test"
|
|
397
|
+
if (/(eslint|lint|ruff|flake8|pylint|clippy)/.test(hint) && eslintRows(source).length) return "lint"
|
|
398
|
+
if (/(typecheck|tsc|compile|build|gradle|maven|cargo check|dotnet build)/.test(hint) && genericBuildRows(source).length) return "build"
|
|
399
|
+
if (/(git|diff|status)/.test(hint) && gitDiffRows(source).length) return "git"
|
|
400
|
+
if ((/(json|--json)/.test(hint) || /^[\s]*[\[{]/.test(String(source || ""))) && jsonRows(source).length) return "json"
|
|
401
|
+
return uniqueSignalLines(source).length ? "signal" : "generic"
|
|
168
402
|
}
|
|
169
403
|
|
|
170
404
|
function boundedPreview(text, maxChars, options = {}) {
|
|
171
405
|
const source = String(text || "")
|
|
172
|
-
const headBudget = Math.max(
|
|
173
|
-
const tailBudget = Math.max(
|
|
174
|
-
const signalBudget = Math.max(2048, maxChars - headBudget - tailBudget -
|
|
406
|
+
const headBudget = Math.max(1536, Math.floor(maxChars * 0.18))
|
|
407
|
+
const tailBudget = Math.max(2048, Math.floor(maxChars * 0.24))
|
|
408
|
+
const signalBudget = Math.max(2048, maxChars - headBudget - tailBudget - 1900)
|
|
175
409
|
const head = source.slice(0, headBudget)
|
|
176
410
|
const tail = source.slice(Math.max(head.length, source.length - tailBudget))
|
|
177
|
-
const
|
|
178
|
-
let signalText =
|
|
179
|
-
if (signalText.length > signalBudget)
|
|
180
|
-
|
|
411
|
+
const reduced = reduceCommandOutput(source, options)
|
|
412
|
+
let signalText = [reduced.summary, ...reduced.rows].join("\n")
|
|
413
|
+
if (signalText.length > signalBudget) {
|
|
414
|
+
signalText = signalText.slice(0, signalBudget) + "\n...[reducer output truncated]"
|
|
415
|
+
}
|
|
416
|
+
return { head, tail, signalText, reducer: reduced }
|
|
181
417
|
}
|
|
182
418
|
|
|
183
419
|
export async function compactReversibleOutput(root, text, options = {}) {
|
|
@@ -185,9 +421,10 @@ export async function compactReversibleOutput(root, text, options = {}) {
|
|
|
185
421
|
const maxChars = clampInt(options.maxChars, DEFAULT_LIMIT, 8 * 1024, 256 * 1024)
|
|
186
422
|
if (source.length <= maxChars) {
|
|
187
423
|
return {
|
|
188
|
-
schemaVersion:
|
|
424
|
+
schemaVersion: 2,
|
|
189
425
|
compacted: false,
|
|
190
426
|
strategy: "raw",
|
|
427
|
+
commandFamily: null,
|
|
191
428
|
originalChars: source.length,
|
|
192
429
|
returnedChars: source.length,
|
|
193
430
|
evidenceRef: null,
|
|
@@ -195,35 +432,45 @@ export async function compactReversibleOutput(root, text, options = {}) {
|
|
|
195
432
|
}
|
|
196
433
|
}
|
|
197
434
|
|
|
435
|
+
// Raw bytes are persisted before any model-visible reduction. Reducers never
|
|
436
|
+
// become the verification source of truth; verifiers can recover exact data.
|
|
198
437
|
const evidence = await putEvidence(root, source, {
|
|
199
438
|
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)`,
|
|
439
|
+
source: options.source || options.command || "ues-performance-fabric-v2",
|
|
440
|
+
summary: options.summary || `Raw output preserved before command-aware model-visible compaction (${source.length} chars)`,
|
|
202
441
|
})
|
|
203
|
-
const { head, tail, signalText,
|
|
442
|
+
const { head, tail, signalText, reducer } = boundedPreview(source, maxChars, options)
|
|
204
443
|
const recovery = `Raw captured output: ${evidence.ref}. Recover exact captured bytes in slices with: ues store get ${evidence.ref} --start N --max 24000`
|
|
205
444
|
let preview = [
|
|
206
|
-
`[UES reversible
|
|
445
|
+
`[UES command-aware reversible compaction v2: ${source.length} -> <=${maxChars} chars; reducer=${reducer.family}]`,
|
|
207
446
|
recovery,
|
|
208
447
|
"",
|
|
209
|
-
"---
|
|
448
|
+
"--- reducer summary ---",
|
|
449
|
+
signalText,
|
|
450
|
+
"",
|
|
451
|
+
"--- bounded head ---",
|
|
210
452
|
head,
|
|
211
|
-
|
|
212
|
-
"
|
|
453
|
+
"",
|
|
454
|
+
"--- bounded tail ---",
|
|
213
455
|
tail,
|
|
214
456
|
].filter(Boolean).join("\n")
|
|
215
457
|
if (preview.length > maxChars) {
|
|
216
|
-
const reserve = Math.min(
|
|
458
|
+
const reserve = Math.min(1500, recovery.length + 200)
|
|
217
459
|
preview = preview.slice(0, Math.max(0, maxChars - reserve)) + `\n...[model-visible preview truncated]\n${recovery}`
|
|
218
460
|
}
|
|
219
461
|
|
|
462
|
+
const compatibilityKind = legacyStrategyKind(source, options)
|
|
220
463
|
return {
|
|
221
|
-
schemaVersion:
|
|
464
|
+
schemaVersion: 2,
|
|
222
465
|
compacted: true,
|
|
223
|
-
strategy
|
|
466
|
+
// Keep the V14 public strategy contract stable for callers/tests while
|
|
467
|
+
// exposing the richer V2 reducer identity separately.
|
|
468
|
+
strategy: `reversible-head-${compatibilityKind}-tail`,
|
|
469
|
+
strategyV2: `reversible-command-aware-${reducer.family}`,
|
|
470
|
+
commandFamily: reducer.family,
|
|
224
471
|
originalChars: source.length,
|
|
225
472
|
returnedChars: preview.length,
|
|
226
473
|
evidenceRef: evidence.ref,
|
|
227
474
|
text: preview,
|
|
228
475
|
}
|
|
229
|
-
}
|
|
476
|
+
}
|