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.
Files changed (37) hide show
  1. package/README.md +3 -3
  2. package/docs/V15-MANAGED-RUNTIME.md +18 -0
  3. package/global-config/agents/verifier.md +6 -2
  4. package/lib/affected-tests.mjs +21 -4
  5. package/lib/capability-fabric.mjs +107 -48
  6. package/lib/code-intelligence/index.mjs +3 -15
  7. package/lib/code-intelligence/lsp-pool.mjs +694 -0
  8. package/lib/code-intelligence/lsp-provider.mjs +160 -20
  9. package/lib/compaction-resume-guard.mjs +76 -4
  10. package/lib/completion-auditor.mjs +20 -0
  11. package/lib/context-manifest.mjs +38 -21
  12. package/lib/decision-policy.mjs +40 -2
  13. package/lib/evidence-store.mjs +121 -31
  14. package/lib/executable-probe.mjs +69 -0
  15. package/lib/hierarchical-context.mjs +31 -3
  16. package/lib/learning-engine.mjs +164 -65
  17. package/lib/memory-engine.mjs +163 -86
  18. package/lib/model-config.mjs +86 -7
  19. package/lib/performance-fabric.mjs +92 -0
  20. package/lib/permission-policy.mjs +232 -0
  21. package/lib/pi-rpc-pool.mjs +170 -5
  22. package/lib/provider-recovery.mjs +51 -0
  23. package/lib/repo-graph.mjs +38 -5
  24. package/lib/runtime-events.mjs +31 -10
  25. package/lib/semantic-index.mjs +67 -20
  26. package/lib/skill-compiler.mjs +84 -18
  27. package/lib/task-policy.mjs +80 -7
  28. package/lib/trajectory.mjs +25 -1
  29. package/lib/untrusted-output.mjs +106 -0
  30. package/package.json +8 -6
  31. package/pi/extensions/ues-child-runtime.ts +78 -3
  32. package/pi/extensions/ues.ts +695 -81
  33. package/scripts/benchmark-lsp-pool.mjs +128 -0
  34. package/scripts/check-release-consistency.mjs +16 -4
  35. package/scripts/check-source-integrity.mjs +60 -1
  36. package/scripts/run-test-suite.mjs +143 -0
  37. package/scripts/smoke-package-closure.mjs +4 -0
@@ -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
- const timestamp = now()
124
- const existing = state.memories.find((item) => item.id === id) ||
125
- state.memories.find((item) => sameMemoryIdentity(item, { ...input, type, scope, content }))
126
- if (existing) {
127
- const reinforced = {
128
- ...existing,
129
- lastSeenAt: timestamp,
130
- reinforcementCount: Number(existing.reinforcementCount || 1) + 1,
131
- evidenceRefs: uniq([...(existing.evidenceRefs || []), ...(input.evidenceRefs || [])]),
132
- files: uniq([...(existing.files || []), ...(input.files || []).map(normalizeFile)]),
133
- dependencies: uniq([...(existing.dependencies || []), ...(input.dependencies || []).map(normalizeFile)]),
134
- tags: uniq([...(existing.tags || []), ...(input.tags || [])]),
135
- confidence: Math.max(clamp(existing.confidence), clamp(input.confidence, existing.confidence ?? 0.7)),
136
- taskClass: input.taskClass || existing.taskClass || null,
137
- expiresAt: input.expiresAt || existing.expiresAt || null,
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
- const memory = {
144
- id,
145
- key: input.key || null,
146
- type,
147
- scope,
148
- content,
149
- status: "candidate",
150
- confidence: clamp(input.confidence),
151
- evidenceRefs: uniq(input.evidenceRefs || []),
152
- files: uniq((input.files || []).map(normalizeFile)),
153
- dependencies: uniq((input.dependencies || []).map(normalizeFile)),
154
- tags: uniq(input.tags || []),
155
- sourceTask: input.sourceTask || null,
156
- sourceCommit: input.sourceCommit || null,
157
- taskClass: input.taskClass || null,
158
- expiresAt: input.expiresAt || null,
159
- createdAt: timestamp,
160
- lastSeenAt: timestamp,
161
- lastUsedAt: null,
162
- useCount: 0,
163
- reinforcementCount: 1,
164
- verifiedAt: null,
165
- verifier: null,
166
- supersededBy: null,
167
- }
168
- await writeMemoryState(root, [...state.memories, memory])
169
- return memory
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
- const verified = {
189
- ...current,
190
- status: "verified",
191
- evidenceRefs: refs,
192
- verifier: String(verification.verifier),
193
- verifiedAt: now(),
194
- lastSeenAt: now(),
195
- confidence: Math.max(clamp(current.confidence), clamp(verification.confidence, current.confidence ?? 0.7)),
196
- }
197
- await writeMemoryState(root, state.memories.map((item) => item.id === id ? verified : item))
198
- return verified
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
- const state = await readMemoryState(root)
204
- const current = state.memories.find((item) => item.id === id)
205
- const replacement = state.memories.find((item) => item.id === replacementId)
206
- if (!current) throw new Error("unknown memory: " + id)
207
- if (!replacement) throw new Error("unknown replacement memory: " + replacementId)
208
- if (replacement.status !== "verified") throw new Error("replacement memory must be verified before supersession")
209
- const timestamp = now()
210
- const superseded = {
211
- ...current,
212
- status: "superseded",
213
- supersededBy: replacementId,
214
- lastSeenAt: timestamp,
215
- }
216
- await writeMemoryState(root, state.memories.map((item) => item.id === id ? superseded : item))
217
- return superseded
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 writeMemoryState(root, state.memories.map((item) => used.has(item.id)
554
- ? { ...item, lastUsedAt: timestamp, useCount: Number(item.useCount || 0) + 1 }
555
- : item))
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,
@@ -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
- export async function writeModelPolicy(configDir, patch = {}) {
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
- await writeFile(file, JSON.stringify(merged, null, 2) + "\n", "utf8")
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
- const current = await readModelPolicy(configDir)
106
- const performance = recordPerformanceOutcome(current.performance || {}, outcome)
107
- return writeModelPolicy(configDir, { performance })
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),