opencode-agent-skill 15.1.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 +3 -3
- package/docs/V15-MANAGED-RUNTIME.md +18 -0
- package/global-config/agents/verifier.md +6 -2
- package/lib/affected-tests.mjs +21 -4
- package/lib/capability-fabric.mjs +107 -48
- package/lib/code-intelligence/index.mjs +3 -15
- package/lib/code-intelligence/lsp-pool.mjs +694 -0
- package/lib/code-intelligence/lsp-provider.mjs +160 -20
- package/lib/compaction-resume-guard.mjs +76 -4
- package/lib/completion-auditor.mjs +20 -0
- 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/hierarchical-context.mjs +31 -3
- package/lib/learning-engine.mjs +164 -65
- package/lib/memory-engine.mjs +163 -86
- package/lib/model-config.mjs +86 -7
- package/lib/performance-fabric.mjs +92 -0
- 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/task-policy.mjs +80 -7
- package/lib/trajectory.mjs +25 -1
- package/lib/untrusted-output.mjs +106 -0
- package/package.json +8 -6
- package/pi/extensions/ues-child-runtime.ts +78 -3
- package/pi/extensions/ues.ts +695 -81
- 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
package/lib/memory-engine.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
2
|
import { existsSync } from "node:fs"
|
|
3
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
3
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises"
|
|
4
4
|
import { spawnSync } from "node:child_process"
|
|
5
5
|
import path from "node:path"
|
|
6
6
|
import { evidenceExists, putEvidence } from "./evidence-store.mjs"
|
|
@@ -16,6 +16,72 @@ const STOP = new Set([
|
|
|
16
16
|
"file","files","change","changes","được","các","cho","với","trong","này","một","những","không","theo",
|
|
17
17
|
])
|
|
18
18
|
|
|
19
|
+
const MEMORY_WRITE_TAILS = new Map()
|
|
20
|
+
const MEMORY_LOCK_STALE_MS = 15_000
|
|
21
|
+
const MEMORY_LOCK_WAIT_MS = 20_000
|
|
22
|
+
|
|
23
|
+
function sleep(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function memoryLockPath(root) {
|
|
28
|
+
return path.join(path.resolve(root), MEMORY_DIR, ".memory-write.lock")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function withCrossProcessMemoryLock(root, fn) {
|
|
32
|
+
const lockDir = memoryLockPath(root)
|
|
33
|
+
await mkdir(path.dirname(lockDir), { recursive: true })
|
|
34
|
+
const deadline = Date.now() + MEMORY_LOCK_WAIT_MS
|
|
35
|
+
let delayMs = 8
|
|
36
|
+
|
|
37
|
+
while (true) {
|
|
38
|
+
try {
|
|
39
|
+
await mkdir(lockDir)
|
|
40
|
+
break
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error?.code !== "EEXIST") throw error
|
|
43
|
+
const info = await stat(lockDir).catch(() => null)
|
|
44
|
+
if (info && Date.now() - info.mtimeMs > MEMORY_LOCK_STALE_MS) {
|
|
45
|
+
const confirmed = await stat(lockDir).catch(() => null)
|
|
46
|
+
if (
|
|
47
|
+
confirmed &&
|
|
48
|
+
confirmed.ino === info.ino &&
|
|
49
|
+
confirmed.mtimeMs === info.mtimeMs
|
|
50
|
+
) {
|
|
51
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (Date.now() >= deadline) throw new Error("Timed out waiting for memory write lock")
|
|
56
|
+
await sleep(delayMs)
|
|
57
|
+
delayMs = Math.min(100, delayMs * 2)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return await fn()
|
|
63
|
+
} finally {
|
|
64
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function withMemoryWriteLock(root, fn) {
|
|
69
|
+
const key = path.resolve(root)
|
|
70
|
+
const previous = MEMORY_WRITE_TAILS.get(key) || Promise.resolve()
|
|
71
|
+
let release
|
|
72
|
+
const barrier = new Promise((resolve) => { release = resolve })
|
|
73
|
+
const tail = previous.catch(() => {}).then(() => barrier)
|
|
74
|
+
MEMORY_WRITE_TAILS.set(key, tail)
|
|
75
|
+
|
|
76
|
+
await previous.catch(() => {})
|
|
77
|
+
try {
|
|
78
|
+
return await withCrossProcessMemoryLock(root, fn)
|
|
79
|
+
} finally {
|
|
80
|
+
release()
|
|
81
|
+
if (MEMORY_WRITE_TAILS.get(key) === tail) MEMORY_WRITE_TAILS.delete(key)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
19
85
|
function now() {
|
|
20
86
|
return new Date().toISOString()
|
|
21
87
|
}
|
|
@@ -118,55 +184,58 @@ export async function proposeMemory(root, input = {}) {
|
|
|
118
184
|
if (!content) throw new Error("memory content is required")
|
|
119
185
|
const type = VALID_TYPES.has(input.type) ? input.type : "episodic"
|
|
120
186
|
const scope = VALID_SCOPES.has(input.scope) ? input.scope : "project"
|
|
121
|
-
const state = await readMemoryState(root)
|
|
122
187
|
const id = idFor({ ...input, type, scope, content })
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
state
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
...
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
188
|
+
|
|
189
|
+
return withMemoryWriteLock(root, async () => {
|
|
190
|
+
const state = await readMemoryState(root)
|
|
191
|
+
const timestamp = now()
|
|
192
|
+
const existing = state.memories.find((item) => item.id === id) ||
|
|
193
|
+
state.memories.find((item) => sameMemoryIdentity(item, { ...input, type, scope, content }))
|
|
194
|
+
if (existing) {
|
|
195
|
+
const reinforced = {
|
|
196
|
+
...existing,
|
|
197
|
+
lastSeenAt: timestamp,
|
|
198
|
+
reinforcementCount: Number(existing.reinforcementCount || 1) + 1,
|
|
199
|
+
evidenceRefs: uniq([...(existing.evidenceRefs || []), ...(input.evidenceRefs || [])]),
|
|
200
|
+
files: uniq([...(existing.files || []), ...(input.files || []).map(normalizeFile)]),
|
|
201
|
+
dependencies: uniq([...(existing.dependencies || []), ...(input.dependencies || []).map(normalizeFile)]),
|
|
202
|
+
tags: uniq([...(existing.tags || []), ...(input.tags || [])]),
|
|
203
|
+
confidence: Math.max(clamp(existing.confidence), clamp(input.confidence, existing.confidence ?? 0.7)),
|
|
204
|
+
taskClass: input.taskClass || existing.taskClass || null,
|
|
205
|
+
expiresAt: input.expiresAt || existing.expiresAt || null,
|
|
206
|
+
}
|
|
207
|
+
await writeMemoryState(root, state.memories.map((item) => item.id === existing.id ? reinforced : item))
|
|
208
|
+
return reinforced
|
|
138
209
|
}
|
|
139
|
-
await writeMemoryState(root, state.memories.map((item) => item.id === id ? reinforced : item))
|
|
140
|
-
return reinforced
|
|
141
|
-
}
|
|
142
210
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
211
|
+
const memory = {
|
|
212
|
+
id,
|
|
213
|
+
key: input.key || null,
|
|
214
|
+
type,
|
|
215
|
+
scope,
|
|
216
|
+
content,
|
|
217
|
+
status: "candidate",
|
|
218
|
+
confidence: clamp(input.confidence),
|
|
219
|
+
evidenceRefs: uniq(input.evidenceRefs || []),
|
|
220
|
+
files: uniq((input.files || []).map(normalizeFile)),
|
|
221
|
+
dependencies: uniq((input.dependencies || []).map(normalizeFile)),
|
|
222
|
+
tags: uniq(input.tags || []),
|
|
223
|
+
sourceTask: input.sourceTask || null,
|
|
224
|
+
sourceCommit: input.sourceCommit || null,
|
|
225
|
+
taskClass: input.taskClass || null,
|
|
226
|
+
expiresAt: input.expiresAt || null,
|
|
227
|
+
createdAt: timestamp,
|
|
228
|
+
lastSeenAt: timestamp,
|
|
229
|
+
lastUsedAt: null,
|
|
230
|
+
useCount: 0,
|
|
231
|
+
reinforcementCount: 1,
|
|
232
|
+
verifiedAt: null,
|
|
233
|
+
verifier: null,
|
|
234
|
+
supersededBy: null,
|
|
235
|
+
}
|
|
236
|
+
await writeMemoryState(root, [...state.memories, memory])
|
|
237
|
+
return memory
|
|
238
|
+
})
|
|
170
239
|
}
|
|
171
240
|
|
|
172
241
|
export async function verifyMemory(root, id, verification = {}) {
|
|
@@ -174,47 +243,52 @@ export async function verifyMemory(root, id, verification = {}) {
|
|
|
174
243
|
throw new Error("memory verification requires verdict PASS")
|
|
175
244
|
}
|
|
176
245
|
if (!verification.verifier) throw new Error("memory verification requires an independent verifier identity")
|
|
177
|
-
const state = await readMemoryState(root)
|
|
178
|
-
const current = state.memories.find((item) => item.id === id)
|
|
179
|
-
if (!current) throw new Error("unknown memory: " + id)
|
|
180
|
-
if (current.status === "superseded") throw new Error("cannot verify a superseded memory")
|
|
181
|
-
|
|
182
|
-
const refs = uniq([...(current.evidenceRefs || []), ...(verification.evidenceRefs || [])])
|
|
183
|
-
if (!refs.length) throw new Error("memory verification requires at least one durable evidence reference")
|
|
184
|
-
for (const ref of refs) {
|
|
185
|
-
if (!(await evidenceExists(root, ref))) throw new Error("memory evidence reference is missing: " + ref)
|
|
186
|
-
}
|
|
187
246
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
247
|
+
return withMemoryWriteLock(root, async () => {
|
|
248
|
+
const state = await readMemoryState(root)
|
|
249
|
+
const current = state.memories.find((item) => item.id === id)
|
|
250
|
+
if (!current) throw new Error("unknown memory: " + id)
|
|
251
|
+
if (current.status === "superseded") throw new Error("cannot verify a superseded memory")
|
|
252
|
+
|
|
253
|
+
const refs = uniq([...(current.evidenceRefs || []), ...(verification.evidenceRefs || [])])
|
|
254
|
+
if (!refs.length) throw new Error("memory verification requires at least one durable evidence reference")
|
|
255
|
+
for (const ref of refs) {
|
|
256
|
+
if (!(await evidenceExists(root, ref))) throw new Error("memory evidence reference is missing: " + ref)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const verified = {
|
|
260
|
+
...current,
|
|
261
|
+
status: "verified",
|
|
262
|
+
evidenceRefs: refs,
|
|
263
|
+
verifier: String(verification.verifier),
|
|
264
|
+
verifiedAt: now(),
|
|
265
|
+
lastSeenAt: now(),
|
|
266
|
+
confidence: Math.max(clamp(current.confidence), clamp(verification.confidence, current.confidence ?? 0.7)),
|
|
267
|
+
}
|
|
268
|
+
await writeMemoryState(root, state.memories.map((item) => item.id === id ? verified : item))
|
|
269
|
+
return verified
|
|
270
|
+
})
|
|
199
271
|
}
|
|
200
272
|
|
|
201
273
|
export async function supersedeMemory(root, id, replacementId) {
|
|
202
274
|
if (!replacementId || replacementId === id) throw new Error("replacement memory id must be different")
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
275
|
+
return withMemoryWriteLock(root, async () => {
|
|
276
|
+
const state = await readMemoryState(root)
|
|
277
|
+
const current = state.memories.find((item) => item.id === id)
|
|
278
|
+
const replacement = state.memories.find((item) => item.id === replacementId)
|
|
279
|
+
if (!current) throw new Error("unknown memory: " + id)
|
|
280
|
+
if (!replacement) throw new Error("unknown replacement memory: " + replacementId)
|
|
281
|
+
if (replacement.status !== "verified") throw new Error("replacement memory must be verified before supersession")
|
|
282
|
+
const timestamp = now()
|
|
283
|
+
const superseded = {
|
|
284
|
+
...current,
|
|
285
|
+
status: "superseded",
|
|
286
|
+
supersededBy: replacementId,
|
|
287
|
+
lastSeenAt: timestamp,
|
|
288
|
+
}
|
|
289
|
+
await writeMemoryState(root, state.memories.map((item) => item.id === id ? superseded : item))
|
|
290
|
+
return superseded
|
|
291
|
+
})
|
|
218
292
|
}
|
|
219
293
|
|
|
220
294
|
function tokenVector(value, dims = 96) {
|
|
@@ -550,9 +624,12 @@ export async function retrieveMemories(root, query, options = {}) {
|
|
|
550
624
|
if (options.touch === true && results.length) {
|
|
551
625
|
const used = new Set(results.map((item) => item.id))
|
|
552
626
|
const timestamp = now()
|
|
553
|
-
await
|
|
554
|
-
|
|
555
|
-
|
|
627
|
+
await withMemoryWriteLock(root, async () => {
|
|
628
|
+
const latest = await readMemoryState(root)
|
|
629
|
+
await writeMemoryState(root, latest.memories.map((item) => used.has(item.id)
|
|
630
|
+
? { ...item, lastUsedAt: timestamp, useCount: Number(item.useCount || 0) + 1 }
|
|
631
|
+
: item))
|
|
632
|
+
})
|
|
556
633
|
}
|
|
557
634
|
return {
|
|
558
635
|
schemaVersion: SCHEMA_VERSION,
|
package/lib/model-config.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
|
2
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises"
|
|
3
3
|
import path from "node:path"
|
|
4
4
|
import { defaultModelPolicy, resolveModel } from "./model-policy.mjs"
|
|
5
5
|
import { normalizeCapabilityProfile } from "./capability-registry.mjs"
|
|
@@ -7,6 +7,70 @@ import { normalizePerformanceHistory, recordPerformanceOutcome } from "./model-p
|
|
|
7
7
|
|
|
8
8
|
const TIERS = new Set(["light", "standard", "heavy"])
|
|
9
9
|
|
|
10
|
+
const MODEL_POLICY_WRITE_TAILS = new Map()
|
|
11
|
+
const MODEL_POLICY_LOCK_STALE_MS = 15_000
|
|
12
|
+
const MODEL_POLICY_LOCK_WAIT_MS = 20_000
|
|
13
|
+
|
|
14
|
+
function sleep(ms) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function modelPolicyLockDir(configDir) {
|
|
19
|
+
return modelPolicyFile(configDir) + ".lock"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function withCrossProcessModelPolicyLock(configDir, fn) {
|
|
23
|
+
const lockDir = modelPolicyLockDir(configDir)
|
|
24
|
+
await mkdir(path.dirname(lockDir), { recursive: true })
|
|
25
|
+
const deadline = Date.now() + MODEL_POLICY_LOCK_WAIT_MS
|
|
26
|
+
let delayMs = 8
|
|
27
|
+
while (true) {
|
|
28
|
+
try {
|
|
29
|
+
await mkdir(lockDir)
|
|
30
|
+
break
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error?.code !== "EEXIST") throw error
|
|
33
|
+
const info = await stat(lockDir).catch(() => null)
|
|
34
|
+
if (info && Date.now() - info.mtimeMs > MODEL_POLICY_LOCK_STALE_MS) {
|
|
35
|
+
const confirmed = await stat(lockDir).catch(() => null)
|
|
36
|
+
if (
|
|
37
|
+
confirmed &&
|
|
38
|
+
confirmed.ino === info.ino &&
|
|
39
|
+
confirmed.mtimeMs === info.mtimeMs
|
|
40
|
+
) {
|
|
41
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (Date.now() >= deadline) throw new Error("Timed out waiting for model-policy write lock")
|
|
46
|
+
await sleep(delayMs)
|
|
47
|
+
delayMs = Math.min(100, delayMs * 2)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return await fn()
|
|
52
|
+
} finally {
|
|
53
|
+
await rm(lockDir, { recursive: true, force: true }).catch(() => {})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function withModelPolicyWriteLock(configDir, fn) {
|
|
58
|
+
const key = path.resolve(configDir)
|
|
59
|
+
const previous = MODEL_POLICY_WRITE_TAILS.get(key) || Promise.resolve()
|
|
60
|
+
let release
|
|
61
|
+
const barrier = new Promise((resolve) => { release = resolve })
|
|
62
|
+
const tail = previous.catch(() => {}).then(() => barrier)
|
|
63
|
+
MODEL_POLICY_WRITE_TAILS.set(key, tail)
|
|
64
|
+
|
|
65
|
+
await previous.catch(() => {})
|
|
66
|
+
try {
|
|
67
|
+
return await withCrossProcessModelPolicyLock(configDir, fn)
|
|
68
|
+
} finally {
|
|
69
|
+
release()
|
|
70
|
+
if (MODEL_POLICY_WRITE_TAILS.get(key) === tail) MODEL_POLICY_WRITE_TAILS.delete(key)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
10
74
|
function validModelID(value) {
|
|
11
75
|
return typeof value === "string" && /^[^\s/]+\/[^\s#]+(?:#[^\s]+)?$/.test(value)
|
|
12
76
|
}
|
|
@@ -61,8 +125,7 @@ export async function readModelPolicy(configDir) {
|
|
|
61
125
|
}
|
|
62
126
|
}
|
|
63
127
|
|
|
64
|
-
|
|
65
|
-
const current = await readModelPolicy(configDir)
|
|
128
|
+
async function persistModelPolicy(configDir, current, patch = {}) {
|
|
66
129
|
const merged = normalize({
|
|
67
130
|
...current,
|
|
68
131
|
...patch,
|
|
@@ -73,10 +136,24 @@ export async function writeModelPolicy(configDir, patch = {}) {
|
|
|
73
136
|
})
|
|
74
137
|
const file = modelPolicyFile(configDir)
|
|
75
138
|
await mkdir(path.dirname(file), { recursive: true })
|
|
76
|
-
|
|
139
|
+
const temp = file + "." + process.pid + "." + Date.now() + ".tmp"
|
|
140
|
+
await writeFile(temp, JSON.stringify(merged, null, 2) + "\n", "utf8")
|
|
141
|
+
try {
|
|
142
|
+
await rename(temp, file)
|
|
143
|
+
} catch (error) {
|
|
144
|
+
await rm(temp, { force: true }).catch(() => {})
|
|
145
|
+
throw error
|
|
146
|
+
}
|
|
77
147
|
return { ...merged, file }
|
|
78
148
|
}
|
|
79
149
|
|
|
150
|
+
export async function writeModelPolicy(configDir, patch = {}) {
|
|
151
|
+
return withModelPolicyWriteLock(configDir, async () => {
|
|
152
|
+
const current = await readModelPolicy(configDir)
|
|
153
|
+
return persistModelPolicy(configDir, current, patch)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
80
157
|
export function validateModelID(value) {
|
|
81
158
|
return validModelID(value)
|
|
82
159
|
}
|
|
@@ -102,7 +179,9 @@ export function applyConfiguredModel(source, role, policy) {
|
|
|
102
179
|
}
|
|
103
180
|
|
|
104
181
|
export async function recordModelPerformance(configDir, outcome = {}) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
182
|
+
return withModelPolicyWriteLock(configDir, async () => {
|
|
183
|
+
const current = await readModelPolicy(configDir)
|
|
184
|
+
const performance = recordPerformanceOutcome(current.performance || {}, outcome)
|
|
185
|
+
return persistModelPolicy(configDir, current, { performance })
|
|
186
|
+
})
|
|
108
187
|
}
|
|
@@ -91,6 +91,73 @@ function gitDiffRows(source, limit = 140) {
|
|
|
91
91
|
], limit)
|
|
92
92
|
}
|
|
93
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
|
+
|
|
94
161
|
function rgRows(source, limit = 100) {
|
|
95
162
|
const lines = String(source || "").split(/\r?\n/).filter(Boolean)
|
|
96
163
|
const matched = []
|
|
@@ -213,6 +280,16 @@ const REDUCERS = Object.freeze([
|
|
|
213
280
|
match: (hint) => /\b(?:pytest|py\.test)\b/.test(hint),
|
|
214
281
|
rows: pytestRows,
|
|
215
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
|
+
},
|
|
216
293
|
{
|
|
217
294
|
family: "git-diff",
|
|
218
295
|
match: (hint) => /\bgit\s+(?:diff|show)\b/.test(hint),
|
|
@@ -248,6 +325,21 @@ const REDUCERS = Object.freeze([
|
|
|
248
325
|
match: (hint) => /\bprisma\b|schema\.prisma/.test(hint),
|
|
249
326
|
rows: prismaRows,
|
|
250
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
|
+
},
|
|
251
343
|
{
|
|
252
344
|
family: "build",
|
|
253
345
|
match: (hint) => /\b(?:compile|build|gradle|maven|mvn|cargo\s+check|dotnet\s+build)\b/.test(hint),
|