@7n/test 0.8.0 → 0.10.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/CHANGELOG.md +28 -0
- package/package.json +4 -2
- package/src/assess-need.mjs +4 -1
- package/src/classify-exports.mjs +108 -0
- package/src/coverage-classify/docs/apply.md +28 -0
- package/src/coverage-classify/docs/cache.md +34 -0
- package/src/coverage-classify/docs/index.md +40 -0
- package/src/coverage-classify/docs/prompt.md +30 -0
- package/src/coverage-classify/docs/verdict-schema.md +29 -0
- package/src/coverage-fix-extract.mjs +10 -10
- package/src/coverage-per-file.mjs +68 -43
- package/src/docs/assess-need.md +31 -0
- package/src/docs/classify-exports.md +30 -0
- package/src/docs/coverage-fix-extract.md +35 -0
- package/src/docs/coverage-fix.md +31 -0
- package/src/docs/coverage-per-file.md +34 -0
- package/src/docs/fix-tests.md +32 -0
- package/src/docs/gen-tests.md +38 -0
- package/src/docs/index.md +34 -0
- package/src/docs/run.md +33 -0
- package/src/fix-tests.mjs +233 -87
- package/src/gen-tests.mjs +1102 -49
- package/src/lib/ast-analyze.mjs +287 -0
- package/src/lib/docs/ast-analyze.md +45 -0
- package/src/lib/docs/index.md +14 -0
- package/src/lib/docs/pi-client.md +29 -0
- package/src/lib/docs/runtime-probe.md +36 -0
- package/src/lib/docs/vitest-shim.md +31 -0
- package/src/lib/pi-client.mjs +18 -3
- package/src/lib/runtime-probe.mjs +390 -0
- package/src/lib/vitest-shim.mjs +73 -0
- package/src/run.mjs +30 -26
- package/src/scripts/lib/changed-files.mjs +5 -5
- package/src/scripts/lib/docs/changed-files.md +32 -0
- package/src/scripts/lib/docs/read-n-cursor-config-lite.md +39 -0
- package/src/scripts/utils/docs/index.md +13 -0
- package/src/scripts/utils/docs/lock-cache-dir.md +32 -0
- package/src/scripts/utils/docs/with-lock.md +38 -0
- package/src/scripts/utils/docs/worktree-fingerprint.md +34 -0
- package/src/scripts/utils/lock-cache-dir.mjs +1 -1
- package/src/scripts/utils/with-lock.mjs +2 -3
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static analysis of JS/ESM source files via oxc (rollup/parseAst).
|
|
3
|
+
*
|
|
4
|
+
* Extracts:
|
|
5
|
+
* - externalMocks — exact vi.mock() lines with shapes derived from import usage
|
|
6
|
+
* - exportedNames — names of exported symbols
|
|
7
|
+
* - internalNames — non-exported top-level declarations
|
|
8
|
+
* - hasSideEffects — top-level call expressions (e.g. checkEnv([...]))
|
|
9
|
+
* - envReads — process.env / env.KEY accesses (for vi.stubEnv hints)
|
|
10
|
+
* - usesFetch — whether source calls fetch()
|
|
11
|
+
*/
|
|
12
|
+
import { parseAstAsync } from 'rollup/parseAst'
|
|
13
|
+
|
|
14
|
+
const FETCH_CALL_RE = /\bfetch\s*\(/
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// AST walker
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Обходить AST та викликає callback для кожного вузла.
|
|
22
|
+
* @param {unknown} node AST-вузол або значення вузла
|
|
23
|
+
* @param {(node: Record<string, unknown>) => void} fn callback для кожного вузла
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
26
|
+
function walk(node, fn) {
|
|
27
|
+
if (!node || typeof node !== 'object') return
|
|
28
|
+
fn(/** @type {Record<string, unknown>} */ (node))
|
|
29
|
+
for (const val of Object.values(node)) {
|
|
30
|
+
if (Array.isArray(val)) {
|
|
31
|
+
for (const child of val) walk(child, fn)
|
|
32
|
+
} else if (val && typeof val === 'object' && val.type) {
|
|
33
|
+
walk(val, fn)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Mock shape derivation
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Collects all member-access chains for a binding name.
|
|
44
|
+
* log.error(x) → ['error']
|
|
45
|
+
* log → [] (direct call/use)
|
|
46
|
+
* @param {unknown} ast parsed AST
|
|
47
|
+
* @param {string} binding import binding name
|
|
48
|
+
* @returns {string[][]} member-access chains for the binding
|
|
49
|
+
*/
|
|
50
|
+
function collectUsagePaths(ast, binding) {
|
|
51
|
+
const paths = []
|
|
52
|
+
walk(ast, node => {
|
|
53
|
+
if (node.type === 'CallExpression' && node.callee?.type === 'Identifier' && node.callee.name === binding) {
|
|
54
|
+
paths.push([])
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
if (node.type === 'MemberExpression' && !node.computed) {
|
|
58
|
+
const chain = []
|
|
59
|
+
let cur = node
|
|
60
|
+
while (cur?.type === 'MemberExpression' && !cur.computed) {
|
|
61
|
+
chain.unshift(cur.property?.name ?? '')
|
|
62
|
+
cur = cur.object
|
|
63
|
+
}
|
|
64
|
+
if (cur?.type === 'Identifier' && cur.name === binding) {
|
|
65
|
+
paths.push(chain)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
return paths
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Builds a nested shape object from usage paths.
|
|
74
|
+
* [['error'], ['warn']] → { error: 'vi.fn()', warn: 'vi.fn()' }
|
|
75
|
+
* [[]] → 'vi.fn()' (direct call)
|
|
76
|
+
* @param {string[][]} paths usage chains for one binding
|
|
77
|
+
* @returns {string | Record<string, unknown>} mock shape
|
|
78
|
+
*/
|
|
79
|
+
function buildShape(paths) {
|
|
80
|
+
if (!paths.length || paths.some(p => p.length === 0)) return 'vi.fn()'
|
|
81
|
+
const shape = {}
|
|
82
|
+
for (const path of paths) {
|
|
83
|
+
let obj = shape
|
|
84
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
85
|
+
if (!obj[path[i]] || typeof obj[path[i]] === 'string') obj[path[i]] = {}
|
|
86
|
+
obj = obj[path[i]]
|
|
87
|
+
}
|
|
88
|
+
const last = path.at(-1)
|
|
89
|
+
if (!obj[last]) obj[last] = 'vi.fn()'
|
|
90
|
+
}
|
|
91
|
+
return shape
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Serializes a mock shape back to formatted JS object text.
|
|
96
|
+
* @param {string | Record<string, unknown>} shape mock shape tree
|
|
97
|
+
* @param {number} depth indentation depth
|
|
98
|
+
* @returns {string} formatted JS object text
|
|
99
|
+
*/
|
|
100
|
+
function serializeShape(shape, depth = 0) {
|
|
101
|
+
if (shape === 'vi.fn()') return 'vi.fn()'
|
|
102
|
+
const pad = ' '.repeat(depth + 1)
|
|
103
|
+
const close = ' '.repeat(depth)
|
|
104
|
+
const entries = Object.entries(shape).map(([k, v]) => `${pad}${k}: ${serializeShape(v, depth + 1)}`)
|
|
105
|
+
return `{
|
|
106
|
+
${entries.join(',\n')}
|
|
107
|
+
${close}}`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Env reads
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Collects env keys read as `env.KEY` or `process.env.KEY`.
|
|
116
|
+
* @param {unknown} ast parsed AST
|
|
117
|
+
* @returns {string[]} unique env keys
|
|
118
|
+
*/
|
|
119
|
+
function collectEnvReads(ast) {
|
|
120
|
+
const keys = new Set()
|
|
121
|
+
walk(ast, node => {
|
|
122
|
+
// env.KEY or process.env.KEY
|
|
123
|
+
if (
|
|
124
|
+
node.type === 'MemberExpression' &&
|
|
125
|
+
!node.computed &&
|
|
126
|
+
node.object?.type === 'Identifier' &&
|
|
127
|
+
(node.object.name === 'env' || node.object.name === 'process')
|
|
128
|
+
) {
|
|
129
|
+
const prop = node.property?.name
|
|
130
|
+
if (prop === 'env') return // process.env itself, not a key
|
|
131
|
+
if (node.object.name === 'env' && prop) {
|
|
132
|
+
keys.add(prop)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (
|
|
136
|
+
node.type === 'MemberExpression' &&
|
|
137
|
+
!node.computed &&
|
|
138
|
+
node.object?.type === 'MemberExpression' &&
|
|
139
|
+
node.object.object?.name === 'process' &&
|
|
140
|
+
node.object.property?.name === 'env' &&
|
|
141
|
+
node.property?.name
|
|
142
|
+
) {
|
|
143
|
+
keys.add(node.property.name)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
return [...keys]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Maps import bindings to package names.
|
|
151
|
+
* @param {unknown} ast parsed AST
|
|
152
|
+
* @returns {Map<string, string>} binding → package map
|
|
153
|
+
*/
|
|
154
|
+
function collectBindingToPkg(ast) {
|
|
155
|
+
const bindingToPkg = new Map()
|
|
156
|
+
for (const node of ast.body) {
|
|
157
|
+
if (node.type !== 'ImportDeclaration') continue
|
|
158
|
+
const pkg = node.source.value
|
|
159
|
+
if (pkg.startsWith('node:') || pkg.startsWith('.')) continue
|
|
160
|
+
for (const spec of node.specifiers ?? []) {
|
|
161
|
+
bindingToPkg.set(spec.local.name, pkg)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return bindingToPkg
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Collects mock shapes for every package based on binding usage.
|
|
169
|
+
* @param {unknown} ast parsed AST
|
|
170
|
+
* @param {Map<string, string>} bindingToPkg binding → package map
|
|
171
|
+
* @returns {Map<string, Record<string, string | Record<string, unknown>>>} package shapes
|
|
172
|
+
*/
|
|
173
|
+
function collectPackageShapes(ast, bindingToPkg) {
|
|
174
|
+
const pkgShapes = new Map()
|
|
175
|
+
for (const [binding, pkg] of bindingToPkg) {
|
|
176
|
+
const paths = collectUsagePaths(ast, binding)
|
|
177
|
+
const shape = buildShape(paths)
|
|
178
|
+
if (!pkgShapes.has(pkg)) pkgShapes.set(pkg, {})
|
|
179
|
+
pkgShapes.get(pkg)[binding] = shape
|
|
180
|
+
}
|
|
181
|
+
return pkgShapes
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Converts package shapes to `vi.mock(...)` lines.
|
|
186
|
+
* @param {Map<string, Record<string, string | Record<string, unknown>>>} pkgShapes package shapes
|
|
187
|
+
* @returns {Array<{pkg: string, mockLine: string}>} mock line definitions
|
|
188
|
+
*/
|
|
189
|
+
function collectExternalMocks(pkgShapes) {
|
|
190
|
+
const externalMocks = []
|
|
191
|
+
for (const [pkg, bindings] of pkgShapes) {
|
|
192
|
+
const shape =
|
|
193
|
+
Object.keys(bindings).length === 1 && Object.values(bindings)[0] === 'vi.fn()'
|
|
194
|
+
? `{ ${Object.keys(bindings)[0]}: vi.fn() }`
|
|
195
|
+
: serializeShape(bindings)
|
|
196
|
+
externalMocks.push({ pkg, mockLine: `vi.mock("${pkg}", () => (${shape}))` })
|
|
197
|
+
}
|
|
198
|
+
return externalMocks
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Collects explicitly exported top-level names.
|
|
203
|
+
* @param {unknown} ast parsed AST
|
|
204
|
+
* @returns {string[]} exported names
|
|
205
|
+
*/
|
|
206
|
+
function collectExportedNames(ast) {
|
|
207
|
+
const exportedNames = []
|
|
208
|
+
for (const node of ast.body) {
|
|
209
|
+
if (node.type !== 'ExportNamedDeclaration' || !node.declaration) continue
|
|
210
|
+
if (node.declaration.type === 'VariableDeclaration') {
|
|
211
|
+
exportedNames.push(...node.declaration.declarations.map(d => d.id?.name).filter(Boolean))
|
|
212
|
+
} else if (node.declaration.id?.name) {
|
|
213
|
+
exportedNames.push(node.declaration.id.name)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return exportedNames
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Collects non-exported top-level declarations.
|
|
221
|
+
* @param {unknown} ast parsed AST
|
|
222
|
+
* @returns {string[]} internal names
|
|
223
|
+
*/
|
|
224
|
+
function collectInternalNames(ast) {
|
|
225
|
+
const internalNames = []
|
|
226
|
+
for (const node of ast.body) {
|
|
227
|
+
if (node.type === 'VariableDeclaration') {
|
|
228
|
+
internalNames.push(...node.declarations.map(d => d.id?.name).filter(Boolean))
|
|
229
|
+
} else if (node.type === 'FunctionDeclaration' && node.id?.name) {
|
|
230
|
+
internalNames.push(node.id.name)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return internalNames
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Detects top-level call expressions.
|
|
238
|
+
* @param {unknown} ast parsed AST
|
|
239
|
+
* @returns {boolean} true when module has top-level side effects
|
|
240
|
+
*/
|
|
241
|
+
function hasTopLevelSideEffects(ast) {
|
|
242
|
+
return ast.body.some(n => n.type === 'ExpressionStatement' && n.expression?.type === 'CallExpression')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Main export
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Analyses ESM source and returns structured info for test generation.
|
|
251
|
+
* @param {string} source JS/ESM source text
|
|
252
|
+
* @returns {Promise<{
|
|
253
|
+
* externalMocks: Array<{pkg: string, mockLine: string}>,
|
|
254
|
+
* exportedNames: string[],
|
|
255
|
+
* internalNames: string[],
|
|
256
|
+
* hasSideEffects: boolean,
|
|
257
|
+
* envReads: string[],
|
|
258
|
+
* usesFetch: boolean,
|
|
259
|
+
* }>} структуровані дані аналізу модуля
|
|
260
|
+
*/
|
|
261
|
+
export async function analyzeModule(source) {
|
|
262
|
+
let ast
|
|
263
|
+
try {
|
|
264
|
+
ast = await parseAstAsync(source)
|
|
265
|
+
} catch {
|
|
266
|
+
return {
|
|
267
|
+
externalMocks: [],
|
|
268
|
+
exportedNames: [],
|
|
269
|
+
internalNames: [],
|
|
270
|
+
hasSideEffects: false,
|
|
271
|
+
envReads: [],
|
|
272
|
+
usesFetch: false
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const bindingToPkg = collectBindingToPkg(ast)
|
|
277
|
+
const pkgShapes = collectPackageShapes(ast, bindingToPkg)
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
externalMocks: collectExternalMocks(pkgShapes),
|
|
281
|
+
exportedNames: collectExportedNames(ast),
|
|
282
|
+
internalNames: collectInternalNames(ast),
|
|
283
|
+
hasSideEffects: hasTopLevelSideEffects(ast),
|
|
284
|
+
envReads: collectEnvReads(ast),
|
|
285
|
+
usesFetch: FETCH_CALL_RE.test(source)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: ast-analyze.mjs
|
|
4
|
+
resource: npm/src/lib/ast-analyze.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 7e4d00fc
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
9
|
+
issues: judge:inaccurate:0.98
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Цей модуль виконує статичний аналіз файлів JS/ESM за допомогою oxc. Він витягує точні рядки `vi.mock` та відповідні форми (shapes) на основі використання імпорту, зіставляє експортовані та внутрішні символи, визначає побічні ефекти, читання змінних середовища (`process.env`/`env.KEY`) та використання `fetch` у коді.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
1. Викликає функцію analyzeModule для аналізу коду.
|
|
20
|
+
2. Парсить вхідний код за допомогою rollup/parseAst. У разі помилки парсингу повертає структуру з порожніми значеннями для всіх полів.
|
|
21
|
+
3. Визначає прив'язки імпортів, зіставляючи їх з зовнішніми пакетами.
|
|
22
|
+
4. Для кожної прив'язки аналізує всі шляхи використання, щоб побудувати структуру заглушки (mock shape).
|
|
23
|
+
5. Формує список `externalMocks` у вигляді рядків `vi.mock` на основі виявлених заглушок.
|
|
24
|
+
6. Сканує тіло коду, щоб зібрати назви всіх символів, які явно експортуються.
|
|
25
|
+
7. Сканує тіло коду, щоб зібрати назви усіх оголошень верхнього рівня (змінних чи функцій), які не є експортованими.
|
|
26
|
+
8. Перевіряє наявність виразів виклику на верхньому рівні, щоб визначити, чи є у модулі побічні ефекти.
|
|
27
|
+
9. Збирає всі ключі, які зчитуються з середовища (`env` або `process.env`).
|
|
28
|
+
10. Перевіряє вихідний код на наявність викликів `fetch`.
|
|
29
|
+
11. Повертає об'єкт, що містить результати: `externalMocks`, `exportedNames`, `internalNames`, `hasSideEffects`, `envReads`, та `usesFetch`.
|
|
30
|
+
|
|
31
|
+
## Публічний API
|
|
32
|
+
|
|
33
|
+
analyzeModule — аналізує вихідний код ESM та повертає структуровану інформацію для створення тестів.
|
|
34
|
+
|
|
35
|
+
externalMocks — масив об'єктів, що містить необхідні моки для зовнішніх пакетів, включаючи сам рядок моку.
|
|
36
|
+
exportedNames — список назв, які експортуються із модуля.
|
|
37
|
+
internalNames — список внутрішніх (неекспортованих) назв елементів у модулі.
|
|
38
|
+
hasSideEffects — булеве значення, що вказує на наявність побічних ефектів у модулі.
|
|
39
|
+
envReads — список змінних середовища, які зчитує модуль.
|
|
40
|
+
usesFetch — булеве значення, що вказує на використання функції `fetch` у модулі.
|
|
41
|
+
|
|
42
|
+
## Гарантії поведінки
|
|
43
|
+
|
|
44
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
45
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/src/lib
|
|
4
|
+
resource: npm/src/lib/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# npm/src/lib
|
|
8
|
+
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ------------------------------------- | --------- |
|
|
11
|
+
| [ast-analyze.mjs](ast-analyze.md) | JS Module |
|
|
12
|
+
| [pi-client.mjs](pi-client.md) | JS Module |
|
|
13
|
+
| [runtime-probe.mjs](runtime-probe.md) | JS Module |
|
|
14
|
+
| [vitest-shim.mjs](vitest-shim.md) | JS Module |
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: pi-client.mjs
|
|
4
|
+
resource: npm/src/lib/pi-client.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 4649ce83
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
9
|
+
issues: judge:inaccurate:0.98
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Цей модуль надає прямий, інпроцесний програмний інтерфейс для взаємодії з агентами pi SDK, замінюючи синхронні виклики зовнішнього процесу. Він реалізує два режими роботи: текстовий режим, де функція `callText` повертає рядок на основі текстового запиту без активації інструментів, та режим агента, де функція `callAgent` активує кодові інструменти для прямого внесення змін у файли у заданій робочій директорії. Зверніть увагу, що у цьому інтерфейсі відсутній механізм кешування.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
callText відправляє текстовий запит до моделі pi та повертає текстову відповідь.
|
|
20
|
+
callAgent відправляє завдання до агента pi з активними інструментами кодування та виконує зміни у файлах у вказаній директорії.
|
|
21
|
+
|
|
22
|
+
## Публічний API
|
|
23
|
+
|
|
24
|
+
callText — надсилає одноразовий запит у текстовому режимі, не використовуючи інструменти, і повертає відповідь, зчитуючи конфігурацію з ~/.pi/.
|
|
25
|
+
callAgent — надсилає запит у режимі агента, надаючи доступ до повного набору інструментів кодування (читання, запис, bash, редагування); агент сам створює тестові файли.
|
|
26
|
+
|
|
27
|
+
## Гарантії поведінки
|
|
28
|
+
|
|
29
|
+
- (специфічних машинно-виведених гарантій немає)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: runtime-probe.mjs
|
|
4
|
+
resource: npm/src/lib/runtime-probe.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 31dd4fd6
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
9
|
+
issues: judge:inaccurate:0.98
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Файл проводить runtime probing експортованих функцій через чотири стратегії: `probeModule` викликає кожен експорт з граничними примітивами для отримання фактичних вихідних даних; `probeFetchCalls` перехоплює глобальні HTTP-запити, щоб фіксувати реальні URL та ініціалізацію для кожного експорту; `probeTimeVariants` запускає кожен експорт у фіксовані часові вікна ([0,9,14,22]) для виявлення чутливих до часу змін; а `probeHelpers` витягує неекспортовані допоміжні функції з вихідного коду для визначення їхніх фактичних форм виходу. Усі стратегії мають принцип best-effort: у разі будь-якої помилки вони повертають порожнє значення, не викидаючи винятків. Функціонал не залежить від кешування.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
Поведінка:
|
|
20
|
+
probeModule викликає кожен експортований функціонал з граничними примітивами, щоб визначити фактичні вихідні дані.
|
|
21
|
+
probeFetchCalls перехоплює глобальний запит `fetch` та збирає реальні URL та ініціалізацію для кожного експортованого функціоналу.
|
|
22
|
+
probeTimeVariants виконує кожен експортований функціонал у різні години доби та доповідає про ті, чий вихід залежить від часу.
|
|
23
|
+
probeHelpers витягує внутрішні допоміжні функції з коду та викликає їх з загальними комбінаціями параметрів для виявлення фактичних форм виходу.
|
|
24
|
+
|
|
25
|
+
## Публічний API
|
|
26
|
+
|
|
27
|
+
probeModule — Виявляє функції, експортовані модулем, за допомогою їх запуску в дочірньому процесі.
|
|
28
|
+
probeFetchCalls — Перехоплює глобальний виклик `fetch` і виконує кожну експортовану асинхронну функцію з різними аргументами, щоб фіксувати фактичний URL та ініціалізацію, які вона конструює.
|
|
29
|
+
probeTimeVariants — Запускає кожну експортовану функцію у різний час доби. Повідомляє про функції, чий результат змінюється залежно від години.
|
|
30
|
+
probeHelpers — Витягує неекспортовані допоміжні функції з вихідного коду та викликає їх із загальними комбінаціями параметрів, щоб модель штучного інтелекту бачила фактичну форму виводу замість припущення.
|
|
31
|
+
|
|
32
|
+
## Гарантії поведінки
|
|
33
|
+
|
|
34
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
35
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
36
|
+
- За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: vitest-shim.mjs
|
|
4
|
+
resource: npm/src/lib/vitest-shim.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 5d7aaaf0
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
9
|
+
issues: judge:inaccurate:0.97
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Цей файл надає спільні утиліти для тестування на основі Vitest. Він дозволяє виконувати тести у цільових проектах, де Vitest не встановлений локально, оскільки використовується Vitest, вбудований у `@7n/test`. Механізм конфігурації (залежить від `package.json`) забезпечує обхід будь-яких помилок завантаження конфігурації у цільовому проекті. Публічні функції включають `VITEST_BIN`, `VITEST_SHIM_CONFIG` та `ensureVitestShim`.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
VITEST_BIN — Абсолютний шлях до вбудованого виконуваного файлу Vitest.
|
|
20
|
+
VITEST_SHIM_CONFIG — Абсолютний шлях до тимчасового файлу конфігурації Vitest, що забезпечує роботу збірки без встановлення Vitest у цільовому проекті.
|
|
21
|
+
ensureVitestShim — Гарантує існування файлу конфігурації Vitest, записуючи мінімальний конфіг у тимчасову директорію, ігноруючи шляхи до `node_modules`.
|
|
22
|
+
|
|
23
|
+
## Публічний API
|
|
24
|
+
|
|
25
|
+
VITEST_BIN — Шлях до виконуваного файлу Vitest, скомпільованого для використання.
|
|
26
|
+
VITEST_SHIM_CONFIG — Мінімальна конфігурація Vitest, яку створюють у тимчасовій директорії ОС, щоб імпортований скомпільований Vitest міг отримати доступ до нього.
|
|
27
|
+
ensureVitestShim — Гарантує існування файлу конфігурації шиму (повторне виконання не змінює стан).
|
|
28
|
+
|
|
29
|
+
## Гарантії поведінки
|
|
30
|
+
|
|
31
|
+
- Свідомо пропускає шляхи: `node_modules`.
|
package/src/lib/pi-client.mjs
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
* callText(prompt, model?) — text-only, no tools, returns string
|
|
7
7
|
* callAgent(prompt, cwd) — coding tools enabled, writes files directly
|
|
8
8
|
*/
|
|
9
|
-
import { createAgentSession, SessionManager } from '@earendil-works/pi-coding-agent'
|
|
9
|
+
import { createAgentSession, SessionManager, ModelRegistry, AuthStorage } from '@earendil-works/pi-coding-agent'
|
|
10
|
+
|
|
11
|
+
let _registry = null
|
|
12
|
+
async function getRegistry() {
|
|
13
|
+
if (_registry) return _registry
|
|
14
|
+
_registry = ModelRegistry.create(AuthStorage.create())
|
|
15
|
+
return _registry
|
|
16
|
+
}
|
|
10
17
|
|
|
11
18
|
/**
|
|
12
19
|
* Sends a single prompt to pi in text mode (no tools) and returns the response.
|
|
@@ -23,8 +30,16 @@ export async function callText(prompt, opts = {}) {
|
|
|
23
30
|
const sessionOpts = {
|
|
24
31
|
tools: [],
|
|
25
32
|
sessionManager: SessionManager.inMemory(cwd),
|
|
26
|
-
cwd
|
|
27
|
-
|
|
33
|
+
cwd
|
|
34
|
+
}
|
|
35
|
+
if (opts.model) {
|
|
36
|
+
const registry = await getRegistry()
|
|
37
|
+
const slashIdx = opts.model.indexOf('/')
|
|
38
|
+
const provider = slashIdx >= 0 ? opts.model.slice(0, slashIdx) : null
|
|
39
|
+
const modelId = slashIdx >= 0 ? opts.model.slice(slashIdx + 1) : opts.model
|
|
40
|
+
const resolved = provider ? registry.find(provider, modelId) : null
|
|
41
|
+
sessionOpts.modelRegistry = registry
|
|
42
|
+
sessionOpts.model = resolved ?? opts.model
|
|
28
43
|
}
|
|
29
44
|
const { session } = await createAgentSession(sessionOpts)
|
|
30
45
|
|