zames_pro 2.54.0 → 2.58.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 +209 -0
- package/README.md +113 -6
- package/dist/agent-loop.js +67 -0
- package/dist/attachments.js +32 -0
- package/dist/commands.js +57 -20
- package/dist/config.js +3 -42
- package/dist/fsutil.js +18 -0
- package/dist/gitTools.js +37 -0
- package/dist/i18n.js +140 -53
- package/dist/index.js +295 -34
- package/dist/input.js +5 -0
- package/dist/mcp.js +20 -0
- package/dist/self-review.js +44 -6
- package/dist/sessions.js +4 -8
- package/dist/shell.js +72 -0
- package/dist/spinner.js +42 -3
- package/dist/system-prompt.js +13 -0
- package/dist/tools.js +43 -75
- package/dist/transcript.js +14 -4
- package/dist/undo.js +8 -6
- package/dist/web.js +31 -0
- package/package.json +14 -4
- package/scripts/clean-dist.mjs +17 -0
- package/scripts/coverage-gate.mjs +83 -0
- package/dist/confirm.js +0 -105
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
// Soft coverage FLOOR for the PURE logic modules — the ones a regression would
|
|
4
|
+
// silently break without any UI symptom (command formatting, schedule parsing,
|
|
5
|
+
// the raw SSE/model-answer parsing). The floors are deliberately a few points
|
|
6
|
+
// BELOW the current values so ordinary churn does not fail the build; they
|
|
7
|
+
// only catch a real drop (a module losing its tests).
|
|
8
|
+
//
|
|
9
|
+
// Node's test runner has no built-in coverage threshold, so we parse the
|
|
10
|
+
// per-file line % from the coverage report it prints and compare here. This is
|
|
11
|
+
// informational in the sense that the FLOORS are conservative, but a drop
|
|
12
|
+
// below one is a hard failure.
|
|
13
|
+
const FLOORS = {
|
|
14
|
+
'commands.ts': 85,
|
|
15
|
+
'scheduler.ts': 90,
|
|
16
|
+
'net-capture.ts': 90,
|
|
17
|
+
'xml-toolcall.ts': 90,
|
|
18
|
+
'diff.ts': 90,
|
|
19
|
+
'theme.ts': 95,
|
|
20
|
+
// Added later: core logic or already well-tested, so a silent regression
|
|
21
|
+
// that deletes their tests should be caught too. Floors sit a few points
|
|
22
|
+
// below the current values.
|
|
23
|
+
'extraTools.ts': 85,
|
|
24
|
+
'fsutil.ts': 90,
|
|
25
|
+
'sessions.ts': 90,
|
|
26
|
+
'context.ts': 85,
|
|
27
|
+
'markdown.ts': 90,
|
|
28
|
+
'tools.ts': 90,
|
|
29
|
+
'system-prompt.ts': 90,
|
|
30
|
+
'undo.ts': 85,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const run = spawnSync(
|
|
34
|
+
'npx',
|
|
35
|
+
['tsx', '--test', '--experimental-test-coverage', 'test/*.test.ts'],
|
|
36
|
+
{ encoding: 'utf-8', shell: process.platform === 'win32', maxBuffer: 1024 * 1024 * 64 },
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const out = (run.stdout || '') + (run.stderr || '')
|
|
40
|
+
|
|
41
|
+
if (run.status !== 0) {
|
|
42
|
+
console.error('coverage-gate: test run failed (exit ' + run.status + ')')
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const linePct = new Map()
|
|
47
|
+
for (const line of out.split('\n')) {
|
|
48
|
+
// Per-file rows look like:
|
|
49
|
+
// ℹ commands.ts | 94.14 | 78.70 | 93.88 | 40-56 ...
|
|
50
|
+
const m = line.match(/^\s*\S?\s*([A-Za-z0-9._-]+\.ts)\s+\|\s+([\d.]+)\s+\|/)
|
|
51
|
+
if (m) linePct.set(m[1], Number(m[2]))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const failures = []
|
|
55
|
+
for (const [file, floor] of Object.entries(FLOORS)) {
|
|
56
|
+
const pct = linePct.get(file)
|
|
57
|
+
if (pct === undefined) {
|
|
58
|
+
failures.push(file + ': no coverage row found')
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
if (pct < floor) {
|
|
62
|
+
failures.push(file + ': ' + pct + '% < floor ' + floor + '%')
|
|
63
|
+
} else {
|
|
64
|
+
console.log('coverage-gate: ' + file + ' ' + pct + '% >= ' + floor + '%')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Print the lowest-covered modules, so a dead or untested module is visible
|
|
69
|
+
// in the CI log even when it has no floor yet. Informational only.
|
|
70
|
+
const sorted = [...linePct.entries()].sort((a, b) => a[1] - b[1]).slice(0, 8)
|
|
71
|
+
if (sorted.length) {
|
|
72
|
+
console.log('coverage-gate: lowest covered modules (informational):')
|
|
73
|
+
for (const [name, pct] of sorted) {
|
|
74
|
+
console.log(' ' + name + ' ' + pct + '%')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (failures.length) {
|
|
79
|
+
console.error('coverage-gate: FAILED')
|
|
80
|
+
for (const f of failures) console.error(' ' + f)
|
|
81
|
+
process.exit(1)
|
|
82
|
+
}
|
|
83
|
+
console.log('coverage-gate: OK')
|
package/dist/confirm.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import * as readlinePromises from 'readline/promises';
|
|
2
|
-
import { theme } from './theme.js';
|
|
3
|
-
import { unifiedDiff, colorDiff } from './diff.js';
|
|
4
|
-
import { translate, DEFAULT_LOCALE } from './i18n.js';
|
|
5
|
-
export class ConfirmManager {
|
|
6
|
-
settings;
|
|
7
|
-
alwaysConfirm;
|
|
8
|
-
allowedForSession;
|
|
9
|
-
locale;
|
|
10
|
-
constructor({ config, locale = DEFAULT_LOCALE, } = {}) {
|
|
11
|
-
const c = config || {};
|
|
12
|
-
this.locale = locale;
|
|
13
|
-
this.settings = {
|
|
14
|
-
write: c.write !== false,
|
|
15
|
-
edit: c.edit !== false,
|
|
16
|
-
bash: c.bash !== false,
|
|
17
|
-
};
|
|
18
|
-
// An invalid operator-supplied regex must NEVER crash the run. Fall back to
|
|
19
|
-
// a literal substring match (case-insensitive) for that pattern.
|
|
20
|
-
this.alwaysConfirm = (c.alwaysConfirm || []).map((p) => {
|
|
21
|
-
try {
|
|
22
|
-
return new RegExp(p, 'i');
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return new RegExp(escapeRegExp(p), 'i');
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
this.allowedForSession = new Set();
|
|
29
|
-
}
|
|
30
|
-
_matchesAlwaysConfirm(command) {
|
|
31
|
-
return this.alwaysConfirm.some((re) => re.test(command));
|
|
32
|
-
}
|
|
33
|
-
shouldAsk(kind, target = null) {
|
|
34
|
-
if (kind === 'bash' && target && this._matchesAlwaysConfirm(target)) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
if (!this.settings[kind])
|
|
38
|
-
return false;
|
|
39
|
-
if (this.allowedForSession.has(kind))
|
|
40
|
-
return false;
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
// Hot-update the language (from /config lang). The manager holds the locale
|
|
44
|
-
// so its prompt follows the interface language without a restart.
|
|
45
|
-
setLocale(locale) {
|
|
46
|
-
this.locale = locale;
|
|
47
|
-
}
|
|
48
|
-
async ask({ kind, target, preview = null, message, }) {
|
|
49
|
-
if (!this.shouldAsk(kind, target ?? null))
|
|
50
|
-
return true;
|
|
51
|
-
if (preview) {
|
|
52
|
-
console.log(preview);
|
|
53
|
-
}
|
|
54
|
-
const t = translate(this.locale);
|
|
55
|
-
const label = message || t('confirm.ask_label') + ' ' + kind + '?';
|
|
56
|
-
const question = theme.warn(`${label} `) +
|
|
57
|
-
theme.system('[') +
|
|
58
|
-
theme.assistant('y') +
|
|
59
|
-
theme.system(' = ' + t('common.yes') + ', ') +
|
|
60
|
-
theme.error('n') +
|
|
61
|
-
theme.system(' = ' + t('common.no') + ', ') +
|
|
62
|
-
theme.user('a') +
|
|
63
|
-
theme.system(' = ' + t('confirm.hint') + '] ');
|
|
64
|
-
const answer = await this._prompt(question);
|
|
65
|
-
if (answer === 'a') {
|
|
66
|
-
if (kind !== 'bash' || !this._matchesAlwaysConfirm(target ?? '')) {
|
|
67
|
-
this.allowedForSession.add(kind);
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
return answer === 'y';
|
|
72
|
-
}
|
|
73
|
-
async _prompt(question) {
|
|
74
|
-
process.stdin.resume();
|
|
75
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
76
|
-
process.stdin.setRawMode(false);
|
|
77
|
-
}
|
|
78
|
-
const rl = readlinePromises.createInterface({
|
|
79
|
-
input: process.stdin,
|
|
80
|
-
output: process.stdout,
|
|
81
|
-
});
|
|
82
|
-
try {
|
|
83
|
-
const ans = await rl.question(question);
|
|
84
|
-
const v = (ans || '').trim().toLowerCase();
|
|
85
|
-
if (v === 'a' || v === 'always' || v === 'в')
|
|
86
|
-
return 'a';
|
|
87
|
-
if (v === 'y' || v === 'yes' || v === 'д' || v === 'да')
|
|
88
|
-
return 'y';
|
|
89
|
-
return 'n';
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
rl.close();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// Escape a string so it can be used as a literal regex (used when an
|
|
97
|
-
// operator-supplied alwaysConfirm pattern is invalid and we fall back to a
|
|
98
|
-
// substring match).
|
|
99
|
-
function escapeRegExp(s) {
|
|
100
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, (m) => '\\' + m);
|
|
101
|
-
}
|
|
102
|
-
export function formatDiffPreview(oldText, newText, { label } = {}) {
|
|
103
|
-
const diff = unifiedDiff(oldText, newText, { label });
|
|
104
|
-
return colorDiff(diff);
|
|
105
|
-
}
|