zames_pro 2.75.5 → 2.76.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 +25 -0
- package/README.md +10 -4
- package/dist/agent-loop.js +61 -1
- package/dist/browser.js +40 -8
- package/dist/commands.js +0 -1
- package/dist/config.js +8 -3
- package/dist/context.js +99 -0
- package/dist/deepseek-ui.js +1 -3
- package/dist/i18n/common.js +13 -16
- package/dist/i18n/config.js +2 -2
- package/dist/i18n/help.js +48 -6
- package/dist/i18n/spinner.js +7 -1
- package/dist/index.js +10 -50
- package/dist/net-capture.js +43 -11
- package/dist/provider-handoff.js +0 -4
- package/dist/qwen-ui.js +0 -8
- package/dist/run-task.js +24 -1
- package/dist/shell.js +0 -6
- package/dist/system-prompt.js +52 -2
- package/dist/tools.js +33 -0
- package/dist/tui/AskQuestion.js +108 -0
- package/dist/tui/FullScreenApp.js +2 -1
- package/dist/tui/InputLine.js +0 -3
- package/dist/tui/StatusBar.js +0 -9
- package/dist/tui/TuiEditor.js +4 -43
- package/dist/tui/edit.js +13 -22
- package/dist/tui/keys.js +12 -14
- package/dist/tui/run-ask.js +22 -0
- package/dist/tui/store.js +0 -2
- package/dist/tui/widgets.js +3 -9
- package/package.json +2 -2
- package/scripts/build-readme.mts +0 -101
- package/scripts/changelog-draft.mts +0 -93
- package/scripts/clean-dist.mts +0 -17
- package/scripts/coverage-gate.mts +0 -112
- package/scripts/ctd-live-driver.mts +0 -51
- package/scripts/ctd-live.tsx +0 -33
- package/scripts/escd-live-driver.mts +0 -37
- package/scripts/eslint-ts6.mts +0 -58
- package/scripts/inline-status-driver.mts +0 -31
- package/scripts/inline-status.tsx +0 -33
- package/scripts/mouse-churn.mts +0 -34
- package/scripts/qwen-models-smoke.mts +0 -117
- package/scripts/qwen-smoke.mts +0 -221
- package/scripts/release.mts +0 -199
- package/scripts/render-demo.mts +0 -154
- package/scripts/render-how-it-works.mts +0 -229
- package/scripts/ru-shot.mts +0 -20
- package/scripts/sched-smoke.mts +0 -150
- package/scripts/self-smoke.mts +0 -309
- package/scripts/tui-comp-shot.mts +0 -63
- package/scripts/tui-components.tsx +0 -62
- package/scripts/tui-config-driver.mts +0 -82
- package/scripts/tui-config-probe.tsx +0 -74
- package/scripts/tui-edit-driver.mts +0 -100
- package/scripts/tui-edit-probe.tsx +0 -38
- package/scripts/tui-editor-probe.tsx +0 -24
- package/scripts/tui-input-driver.mts +0 -54
- package/scripts/tui-input-shot.tsx +0 -59
- package/scripts/tui-log-driver.mts +0 -35
- package/scripts/tui-log-shot.tsx +0 -48
- package/scripts/tui-longpaste-driver.mts +0 -56
- package/scripts/tui-longpaste.tsx +0 -35
- package/scripts/tui-manyselect-shot.mts +0 -34
- package/scripts/tui-manyselect.tsx +0 -17
- package/scripts/tui-mouse-churn-probe.tsx +0 -28
- package/scripts/tui-paste-driver.mts +0 -88
- package/scripts/tui-paste-kinds.tsx +0 -76
- package/scripts/tui-pastetest.mts +0 -38
- package/scripts/tui-probe.tsx +0 -62
- package/scripts/tui-repro.tsx +0 -64
- package/scripts/tui-resize.mts +0 -70
- package/scripts/tui-scroll-driver.mts +0 -53
- package/scripts/tui-scroll-probe.tsx +0 -46
- package/scripts/tui-shot.mts +0 -106
- package/scripts/tui-status-driver.mts +0 -24
- package/scripts/tui-status-shot.tsx +0 -27
- package/scripts/tui-submit.tsx +0 -57
- package/scripts/tui-wheel-driver.mts +0 -61
- package/scripts/tui-wheel-probe.tsx +0 -48
- package/scripts/tui-widgets-driver.mts +0 -20
- package/scripts/tui-widgets.tsx +0 -44
package/dist/tui/keys.js
CHANGED
|
@@ -35,8 +35,8 @@ export function parseCsiU(raw) {
|
|
|
35
35
|
if (cp === 13 && (ctrl || alt))
|
|
36
36
|
return { type: 'newline' };
|
|
37
37
|
if (cp === 3) {
|
|
38
|
-
// Ctrl/Alt+Delete deletes the word on the
|
|
39
|
-
return ctrl || alt ? { type: '
|
|
38
|
+
// Ctrl/Alt+Delete deletes the word on the RIGHT (standard editor binding).
|
|
39
|
+
return ctrl || alt ? { type: 'deleteWordRight' } : { type: 'delete' };
|
|
40
40
|
}
|
|
41
41
|
if (cp === 127) {
|
|
42
42
|
return ctrl || alt ? { type: 'deleteWordLeft' } : { type: 'backspace' };
|
|
@@ -85,11 +85,11 @@ export function dispatchKey(input, key, ctx, isVisualEdge) {
|
|
|
85
85
|
return { type: 'wordLeft' };
|
|
86
86
|
if (key.rightArrow)
|
|
87
87
|
return { type: 'wordRight' };
|
|
88
|
-
// Ctrl+Delete deletes the word
|
|
89
|
-
//
|
|
90
|
-
//
|
|
88
|
+
// Ctrl+Delete deletes the word AFTER the cursor (the standard editor
|
|
89
|
+
// binding, same as the legacy LineEditor). Ctrl+Backspace deletes the word
|
|
90
|
+
// BEFORE it. Plain Delete still deletes one char to the right.
|
|
91
91
|
if (key.delete)
|
|
92
|
-
return { type: '
|
|
92
|
+
return { type: 'deleteWordRight' };
|
|
93
93
|
if (key.backspace)
|
|
94
94
|
return { type: 'deleteWordLeft' };
|
|
95
95
|
if (key.home)
|
|
@@ -127,8 +127,6 @@ export function dispatchKey(input, key, ctx, isVisualEdge) {
|
|
|
127
127
|
return { type: 'suggestPrev' };
|
|
128
128
|
case 'v':
|
|
129
129
|
return { type: 'clipboard' };
|
|
130
|
-
case 'y':
|
|
131
|
-
return { type: 'copyCode' };
|
|
132
130
|
case 'j':
|
|
133
131
|
return { type: 'newline' };
|
|
134
132
|
default:
|
|
@@ -145,16 +143,16 @@ export function dispatchKey(input, key, ctx, isVisualEdge) {
|
|
|
145
143
|
if (input === 'f')
|
|
146
144
|
return { type: 'wordRight' };
|
|
147
145
|
// VSCode sends Ctrl+Delete as `ESC d` (which Ink reports as Alt+d).
|
|
148
|
-
//
|
|
146
|
+
// Standard editor binding: delete the word AFTER the cursor.
|
|
149
147
|
if (input === 'd')
|
|
150
|
-
return { type: '
|
|
148
|
+
return { type: 'deleteWordRight' };
|
|
151
149
|
if (key.backspace)
|
|
152
150
|
return { type: 'deleteWordLeft' };
|
|
153
|
-
// Alt/Ctrl+Delete also deletes the word
|
|
154
|
-
//
|
|
155
|
-
//
|
|
151
|
+
// Alt/Ctrl+Delete also deletes the word AFTER the cursor (consistent with
|
|
152
|
+
// ESC d): some terminals encode Ctrl+Delete as meta+Delete rather than
|
|
153
|
+
// ESC d.
|
|
156
154
|
if (key.delete)
|
|
157
|
-
return { type: '
|
|
155
|
+
return { type: 'deleteWordRight' };
|
|
158
156
|
return null;
|
|
159
157
|
}
|
|
160
158
|
// ---- LF as a bare newline (Ctrl+J) ----
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from 'ink';
|
|
3
|
+
import { AskQuestion } from './AskQuestion.js';
|
|
4
|
+
/**
|
|
5
|
+
* Show the modal question and resolve with the chosen option, the typed text,
|
|
6
|
+
* or null when dismissed / stdin is not a TTY.
|
|
7
|
+
*/
|
|
8
|
+
export async function askQuestionInk(opts) {
|
|
9
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY))
|
|
10
|
+
return null;
|
|
11
|
+
return await new Promise((resolve) => {
|
|
12
|
+
let answered = false;
|
|
13
|
+
const app = render(_jsx(AskQuestion, { ...opts, onResult: (v) => {
|
|
14
|
+
answered = true;
|
|
15
|
+
resolve(v);
|
|
16
|
+
} }), { exitOnCtrlC: false });
|
|
17
|
+
app.waitUntilExit().then(() => {
|
|
18
|
+
if (!answered)
|
|
19
|
+
resolve(null);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
package/dist/tui/store.js
CHANGED
package/dist/tui/widgets.js
CHANGED
|
@@ -42,19 +42,13 @@ export function HintBar({ hints, }) {
|
|
|
42
42
|
return (_jsx(Text, { children: hints.map(([k, l], i) => (_jsxs(Text, { children: [i > 0 ? _jsx(Text, { color: UI.dim, children: ' · ' }) : null, _jsx(Text, { color: UI.accent, children: k }), _jsx(Text, { color: UI.dim, children: ' ' + l })] }, i))) }));
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* A CODE BLOCK with a syntax-highlighted body and
|
|
45
|
+
* A CODE BLOCK with a syntax-highlighted body and an optional title label.
|
|
46
46
|
*
|
|
47
47
|
* The agent's answers are printed as pre-rendered ANSI (markdown.ts already
|
|
48
48
|
* highlights with cli-highlight), so the live log does NOT use this component —
|
|
49
49
|
* it exists for menus/panels that show a snippet inline (and for a future
|
|
50
|
-
* structured log).
|
|
51
|
-
* Pass `onCopy` and show `hint` so the operator learns the binding.
|
|
50
|
+
* structured log).
|
|
52
51
|
*/
|
|
53
|
-
export function CodeBlock({ code, lang, highlighted, width, hint, focused,
|
|
54
|
-
// React cannot call `onCopy` from a keydown here (Ink has no per-element
|
|
55
|
-
// focus ring by default); the caller wires the key and passes `focused` to
|
|
56
|
-
// show which block it will copy. `onCopy` is kept so a future focus manager
|
|
57
|
-
// can invoke it without changing the call sites.
|
|
58
|
-
void onCopy;
|
|
52
|
+
export function CodeBlock({ code, lang, highlighted, width, hint, focused, }) {
|
|
59
53
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: focused ? UI.accent : UI.border, paddingX: 1, width: width, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: UI.dim, children: lang || 'code' }), hint ? _jsx(Text, { color: UI.accent, children: hint }) : null] }), (highlighted || code).split(String.fromCharCode(10)).map((line, i) => (_jsx(Text, { children: line || ' ' }, i)))] }));
|
|
60
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.76.0",
|
|
4
4
|
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen and other web LLMs — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"scripts",
|
|
11
|
+
"scripts/postinstall.mjs",
|
|
12
12
|
"README.md",
|
|
13
13
|
"CHANGELOG.md",
|
|
14
14
|
"LICENSE",
|
package/scripts/build-readme.mts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
// Assemble README.md from the two language sources.
|
|
2
|
-
//
|
|
3
|
-
// Why: npm renders exactly ONE readme per package (README.md in the root) and
|
|
4
|
-
// has no language tabs. npm renders the readme with GitHub Flavored Markdown
|
|
5
|
-
// (via GitHub's API), so a <details> block DOES work there -- and on GitHub
|
|
6
|
-
// too. We keep the English text on top and fold the Russian translation into
|
|
7
|
-
// a collapsible block at the bottom. The language switcher is an in-page
|
|
8
|
-
// `#readme-ru` anchor, so one README.md serves both languages -- there is
|
|
9
|
-
// no separate README.ru.md anymore (it was invisible on npm and just
|
|
10
|
-
// duplicated the folded block).
|
|
11
|
-
//
|
|
12
|
-
// The two sources (docs/readme.en.md, docs/readme.ru.md) are the single point
|
|
13
|
-
// of edit for each language; this script wires the switcher and the collapsible
|
|
14
|
-
// cross-language block so neither language has to be maintained by hand. The
|
|
15
|
-
// generated file is committed; test/readme-built.test.ts re-runs buildReadmes()
|
|
16
|
-
// and fails if it drifts from the sources.
|
|
17
|
-
//
|
|
18
|
-
// Usage: npom run build:readme
|
|
19
|
-
|
|
20
|
-
import fs from 'node:fs'
|
|
21
|
-
import path from 'node:path'
|
|
22
|
-
import { fileURLToPath } from 'node:url'
|
|
23
|
-
|
|
24
|
-
const NL = String.fromCharCode(10)
|
|
25
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
26
|
-
export const SWITCHER = '<!-- SWITCHER -->'
|
|
27
|
-
|
|
28
|
-
// In-page anchor the switcher links to. GitHub strips `id` on some elements but
|
|
29
|
-
// keeps `name` on an <a>, so both are set.
|
|
30
|
-
export const RU_ANCHOR = '<a id="readme-ru" name="readme-ru"></a>'
|
|
31
|
-
|
|
32
|
-
const EN_SWITCH = [
|
|
33
|
-
'<p align="center">',
|
|
34
|
-
' <strong>English</strong> | <a href="#readme-ru">Русский</a>',
|
|
35
|
-
'</p>',
|
|
36
|
-
].join(NL)
|
|
37
|
-
|
|
38
|
-
export function readSource(
|
|
39
|
-
rootDir: string,
|
|
40
|
-
name: string,
|
|
41
|
-
): { head: string; body: string } {
|
|
42
|
-
const file = path.join(rootDir, 'docs', name)
|
|
43
|
-
const text = fs.readFileSync(file, 'utf-8')
|
|
44
|
-
const i = text.indexOf(SWITCHER)
|
|
45
|
-
if (i < 0) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
file + ' has no ' + SWITCHER + ' marker (was it edited by hand?)',
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
// Everything above the marker: logo, title, badges.
|
|
52
|
-
head: text.slice(0, i).replace(/\n+$/, ''),
|
|
53
|
-
// Everything below: the actual documentation.
|
|
54
|
-
body: text
|
|
55
|
-
.slice(i + SWITCHER.length)
|
|
56
|
-
.replace(/^\n+/, '')
|
|
57
|
-
.replace(/\n+$/, ''),
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function details(summary: string, body: string): string {
|
|
62
|
-
return [
|
|
63
|
-
'<details>',
|
|
64
|
-
'<summary>' + summary + '</summary>',
|
|
65
|
-
'',
|
|
66
|
-
body,
|
|
67
|
-
'',
|
|
68
|
-
'</details>',
|
|
69
|
-
].join(NL)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Returns the generated README.md WITHOUT touching the disk, so a test can
|
|
73
|
-
// compare it against the committed file. Only README.md is generated: the
|
|
74
|
-
// Russian translation lives inside it as a <details> block, and the switcher
|
|
75
|
-
// is an in-page `#readme-ru` anchor.
|
|
76
|
-
export function buildReadmes(rootDir: string): { readmeEn: string } {
|
|
77
|
-
const en = readSource(rootDir, 'readme.en.md')
|
|
78
|
-
const ru = readSource(rootDir, 'readme.ru.md')
|
|
79
|
-
const readmeEn = [
|
|
80
|
-
en.head,
|
|
81
|
-
'',
|
|
82
|
-
EN_SWITCH,
|
|
83
|
-
'',
|
|
84
|
-
en.body,
|
|
85
|
-
'',
|
|
86
|
-
RU_ANCHOR,
|
|
87
|
-
details('🇷🇺 Читать по-русски (Russian)', ru.body),
|
|
88
|
-
'',
|
|
89
|
-
].join(NL)
|
|
90
|
-
return { readmeEn }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Run only when invoked directly (not when imported by a test).
|
|
94
|
-
const invoked =
|
|
95
|
-
process.argv[1] &&
|
|
96
|
-
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
97
|
-
if (invoked) {
|
|
98
|
-
const { readmeEn } = buildReadmes(root)
|
|
99
|
-
fs.writeFileSync(path.join(root, 'README.md'), readmeEn)
|
|
100
|
-
console.log('build-readme: wrote README.md')
|
|
101
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import fs from 'node:fs'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import {
|
|
6
|
-
parseConventionalCommit,
|
|
7
|
-
groupCommits,
|
|
8
|
-
renderChangelogDraft,
|
|
9
|
-
syncUnreleased,
|
|
10
|
-
} from '../src/changelog.js'
|
|
11
|
-
|
|
12
|
-
// Draft a CHANGELOG section from conventional commits since a ref (BACKLOG
|
|
13
|
-
// D2). PRINTS to stdout and never touches CHANGELOG.md -- the operator (or
|
|
14
|
-
// the agent) pastes the result in by hand, so a bad commit subject can never
|
|
15
|
-
// corrupt the changelog.
|
|
16
|
-
//
|
|
17
|
-
// Usage:
|
|
18
|
-
// npm run changelog:draft # since the latest tag, or an explicit ref
|
|
19
|
-
// npm run changelog:draft -- v2.63.0
|
|
20
|
-
// npm run changelog:draft -- --write # update the Unreleased section in place
|
|
21
|
-
// npm run changelog:draft -- --check # exit 1 if Unreleased is out of date
|
|
22
|
-
|
|
23
|
-
function git(args: string[]): string {
|
|
24
|
-
const r = spawnSync('git', args, { encoding: 'utf-8' })
|
|
25
|
-
if (r.status !== 0) return ''
|
|
26
|
-
return (r.stdout || '').toString()
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const argv = process.argv.slice(2)
|
|
30
|
-
const write = argv.includes('--write')
|
|
31
|
-
const check = argv.includes('--check')
|
|
32
|
-
const argRef = argv.find((a) => !a.startsWith('-'))
|
|
33
|
-
|
|
34
|
-
// Resolve the start ref: an explicit arg, otherwise the latest tag,
|
|
35
|
-
// otherwise the root commit.
|
|
36
|
-
const latestTag = git(['describe', '--tags', '--abbrev=0']).trim()
|
|
37
|
-
const ref =
|
|
38
|
-
argRef ||
|
|
39
|
-
latestTag ||
|
|
40
|
-
git(['revlist', 'HEAD']).trim().split('\n').pop() ||
|
|
41
|
-
'HEAD'
|
|
42
|
-
|
|
43
|
-
const subjects = git([
|
|
44
|
-
'log',
|
|
45
|
-
'--no-merges',
|
|
46
|
-
'--pretty=format:%s',
|
|
47
|
-
`${ref}..HEAD`,
|
|
48
|
-
])
|
|
49
|
-
.split('\n')
|
|
50
|
-
.map((s) => s.trim())
|
|
51
|
-
.filter(Boolean)
|
|
52
|
-
|
|
53
|
-
const parsed = subjects
|
|
54
|
-
.map(parseConventionalCommit)
|
|
55
|
-
.filter((c): c is NonNullable<typeof c> => c !== null)
|
|
56
|
-
const body = renderChangelogDraft(groupCommits(parsed))
|
|
57
|
-
|
|
58
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
59
|
-
const changelogPath = path.join(root, 'CHANGELOG.md')
|
|
60
|
-
|
|
61
|
-
// D2: sync the `## [Unreleased]` section in CHANGELOG.md from the conventional
|
|
62
|
-
// commits since the last tag. `--write` edits the file; `--check` fails when
|
|
63
|
-
// the committed section is out of date (for CI); neither just prints the body.
|
|
64
|
-
if (write || check) {
|
|
65
|
-
const onDisk = fs.readFileSync(changelogPath, 'utf-8')
|
|
66
|
-
const nextText = syncUnreleased(onDisk, body)
|
|
67
|
-
if (write) {
|
|
68
|
-
if (nextText !== onDisk) {
|
|
69
|
-
fs.writeFileSync(changelogPath, nextText)
|
|
70
|
-
console.log('changelog: updated the Unreleased section')
|
|
71
|
-
} else {
|
|
72
|
-
console.log('changelog: Unreleased already up to date')
|
|
73
|
-
}
|
|
74
|
-
process.exit(0)
|
|
75
|
-
}
|
|
76
|
-
if (nextText !== onDisk) {
|
|
77
|
-
console.error(
|
|
78
|
-
'changelog: the Unreleased section is out of date — run `npm run changelog:sync`',
|
|
79
|
-
)
|
|
80
|
-
process.exit(1)
|
|
81
|
-
}
|
|
82
|
-
console.log('changelog: Unreleased is up to date')
|
|
83
|
-
process.exit(0)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!body) {
|
|
87
|
-
console.error('no conventional commits since ' + ref)
|
|
88
|
-
process.exit(0)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
console.log('## [Unreleased]')
|
|
92
|
-
console.log('')
|
|
93
|
-
console.log(body)
|
package/scripts/clean-dist.mts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { rmSync, existsSync } from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
|
|
5
|
-
// tsc emits into dist/ without clearing it, so a source file DELETED from
|
|
6
|
-
// src/ leaves an orphan .js in the published tree (this really happened:
|
|
7
|
-
// dist/confirm.js survived after src/confirm.ts was removed). Wipe dist/ first
|
|
8
|
-
// so the build output mirrors src/ exactly.
|
|
9
|
-
//
|
|
10
|
-
// Cross-platform: use node's fs.rm instead of a shell `rm -rf` (Windows has no
|
|
11
|
-
// rm) and never touch anything outside this repo.
|
|
12
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
13
|
-
const dist = path.resolve(here, '..', 'dist')
|
|
14
|
-
if (existsSync(dist)) {
|
|
15
|
-
rmSync(dist, { recursive: true, force: true })
|
|
16
|
-
console.log('clean-dist: removed', dist)
|
|
17
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
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
|
-
'checkpoint.ts': 80,
|
|
32
|
-
'permissions.ts': 85,
|
|
33
|
-
// changelog.ts is mostly comments + type/interface declarations (lines
|
|
34
|
-
// 1-30) that carry no runtime statements, so its LINE % caps well below the
|
|
35
|
-
// other modules even with every executable branch tested. The floor reflects
|
|
36
|
-
// that ceiling (a real drop below it still fails).
|
|
37
|
-
'changelog.ts': 78,
|
|
38
|
-
'attach-refs.ts': 85,
|
|
39
|
-
// C3: the pure /config subcommand dispatcher — silent routing regressions
|
|
40
|
-
// (a wrong subcommand branch) are invisible without tests.
|
|
41
|
-
'config-command.ts': 85,
|
|
42
|
-
// The Ink TUI's PURE halves: the editor state transitions (tui/edit.ts) and
|
|
43
|
-
// the key dispatcher (tui/keys.ts). A silent regression there breaks the
|
|
44
|
-
// operator's editing without any UI test catching it. NOTE: the gate keys on
|
|
45
|
-
// the BASENAME (its regex has no slash), so these are 'edit.ts' / 'keys.ts' /
|
|
46
|
-
// 'store.ts' — unique in the tree today.
|
|
47
|
-
'edit.ts': 85,
|
|
48
|
-
'keys.ts': 85,
|
|
49
|
-
'store.ts': 85,
|
|
50
|
-
'banner.ts': 85,
|
|
51
|
-
'tool-summary.ts': 85,
|
|
52
|
-
// N12: web.ts holds the SSRF guard and the DuckDuckGo/HTML parsers — a
|
|
53
|
-
// regression there is silent. Floors sit below the current values.
|
|
54
|
-
'web.ts': 40,
|
|
55
|
-
'spinner.ts': 55,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const run = spawnSync(
|
|
59
|
-
'npx',
|
|
60
|
-
['tsx', '--test', '--experimental-test-coverage', 'test/*.test.ts'],
|
|
61
|
-
{
|
|
62
|
-
encoding: 'utf-8',
|
|
63
|
-
shell: process.platform === 'win32',
|
|
64
|
-
maxBuffer: 1024 * 1024 * 64,
|
|
65
|
-
},
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
const out = (run.stdout || '') + (run.stderr || '')
|
|
69
|
-
|
|
70
|
-
if (run.status !== 0) {
|
|
71
|
-
console.error('coverage-gate: test run failed (exit ' + run.status + ')')
|
|
72
|
-
process.exit(1)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const linePct = new Map()
|
|
76
|
-
for (const line of out.split('\n')) {
|
|
77
|
-
// Per-file rows look like:
|
|
78
|
-
// ℹ commands.ts | 94.14 | 78.70 | 93.88 | 40-56 ...
|
|
79
|
-
const m = line.match(/^\s*\S?\s*([A-Za-z0-9._-]+\.ts)\s+\|\s+([\d.]+)\s+\|/)
|
|
80
|
-
if (m) linePct.set(m[1], Number(m[2]))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const failures = []
|
|
84
|
-
for (const [file, floor] of Object.entries(FLOORS)) {
|
|
85
|
-
const pct = linePct.get(file)
|
|
86
|
-
if (pct === undefined) {
|
|
87
|
-
failures.push(file + ': no coverage row found')
|
|
88
|
-
continue
|
|
89
|
-
}
|
|
90
|
-
if (pct < floor) {
|
|
91
|
-
failures.push(file + ': ' + pct + '% < floor ' + floor + '%')
|
|
92
|
-
} else {
|
|
93
|
-
console.log('coverage-gate: ' + file + ' ' + pct + '% >= ' + floor + '%')
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Print the lowest-covered modules, so a dead or untested module is visible
|
|
98
|
-
// in the CI log even when it has no floor yet. Informational only.
|
|
99
|
-
const sorted = [...linePct.entries()].sort((a, b) => a[1] - b[1]).slice(0, 8)
|
|
100
|
-
if (sorted.length) {
|
|
101
|
-
console.log('coverage-gate: lowest covered modules (informational):')
|
|
102
|
-
for (const [name, pct] of sorted) {
|
|
103
|
-
console.log(' ' + name + ' ' + pct + '%')
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (failures.length) {
|
|
108
|
-
console.error('coverage-gate: FAILED')
|
|
109
|
-
for (const f of failures) console.error(' ' + f)
|
|
110
|
-
process.exit(1)
|
|
111
|
-
}
|
|
112
|
-
console.log('coverage-gate: OK')
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// Drive the live-like probe: type a seed, then send Ctrl+Delete in EVERY
|
|
2
|
-
// encoding a terminal might use, and print the raw bytes + the buffer.
|
|
3
|
-
import { spawn } from 'node:child_process'
|
|
4
|
-
import fs from 'node:fs'
|
|
5
|
-
import path from 'node:path'
|
|
6
|
-
|
|
7
|
-
const ESC = String.fromCharCode(27)
|
|
8
|
-
const LOG = '/tmp/ctd-live.log'
|
|
9
|
-
try {
|
|
10
|
-
fs.unlinkSync(LOG)
|
|
11
|
-
} catch {
|
|
12
|
-
/* ignore */
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const child = spawn(
|
|
16
|
-
'script',
|
|
17
|
-
['-qec', 'npx tsx scripts/ctd-live.tsx', '/dev/null'],
|
|
18
|
-
{
|
|
19
|
-
cwd: path.resolve('.'),
|
|
20
|
-
env: { ...process.env, COLUMNS: '80', LINES: '24', PROBE_CTD_LOG: LOG },
|
|
21
|
-
},
|
|
22
|
-
)
|
|
23
|
-
child.stdout.on('data', () => {})
|
|
24
|
-
child.stderr.on('data', () => {})
|
|
25
|
-
|
|
26
|
-
const seq = process.argv[2] || 'xterm'
|
|
27
|
-
const enc: Record<string, string> = {
|
|
28
|
-
xterm: ESC + '[3;5~',
|
|
29
|
-
kitty: ESC + '[3;5u',
|
|
30
|
-
vt: ESC + '[3;5:1~',
|
|
31
|
-
}
|
|
32
|
-
const bytes = enc[seq] ?? seq
|
|
33
|
-
|
|
34
|
-
setTimeout(() => {
|
|
35
|
-
child.stdin.write('foo bar baz') // seed
|
|
36
|
-
setTimeout(() => {
|
|
37
|
-
child.stdin.write(bytes) // the Ctrl+Delete encoding
|
|
38
|
-
setTimeout(() => {
|
|
39
|
-
const log = fs.readFileSync(LOG, 'utf-8')
|
|
40
|
-
process.stdout.write('=== ' + seq + ' ===' + String.fromCharCode(10))
|
|
41
|
-
process.stdout.write(
|
|
42
|
-
log
|
|
43
|
-
.split(String.fromCharCode(10))
|
|
44
|
-
.filter((l) => l.startsWith('RAW') || l.startsWith('BUF'))
|
|
45
|
-
.join(String.fromCharCode(10)) + String.fromCharCode(10),
|
|
46
|
-
)
|
|
47
|
-
child.kill('SIGTERM')
|
|
48
|
-
}, 500)
|
|
49
|
-
}, 400)
|
|
50
|
-
}, 5000)
|
|
51
|
-
child.on('close', () => process.exit(0))
|
package/scripts/ctd-live.tsx
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// Minimal LIVE-like probe: the REAL TuiEditor exactly as index.ts builds it
|
|
2
|
-
// (default options), plus a raw-stdin TAP that logs every byte the terminal
|
|
3
|
-
// sends, so we can see EXACTLY what Ctrl+Delete produces on this terminal.
|
|
4
|
-
import fs from 'node:fs'
|
|
5
|
-
import { TuiEditor } from '../src/tui/TuiEditor.js'
|
|
6
|
-
|
|
7
|
-
const LOG = process.env.PROBE_CTD_LOG || '/tmp/ctd-live.log'
|
|
8
|
-
fs.writeFileSync(LOG, '')
|
|
9
|
-
const w = (s: string): void => {
|
|
10
|
-
fs.appendFileSync(LOG, s + String.fromCharCode(10))
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const ed = new TuiEditor({
|
|
14
|
-
prompt: '❯ › ',
|
|
15
|
-
commands: [],
|
|
16
|
-
locale: 'ru',
|
|
17
|
-
alternateScreen: true,
|
|
18
|
-
})
|
|
19
|
-
ed.onSubmit = () => {}
|
|
20
|
-
ed.onAttach = () => null
|
|
21
|
-
ed.onClipboard = () => Promise.resolve(null)
|
|
22
|
-
ed.start()
|
|
23
|
-
|
|
24
|
-
// Tap raw stdin: hex-dump every chunk so we see the exact bytes.
|
|
25
|
-
process.stdin.on('data', (d: Buffer) => {
|
|
26
|
-
w('RAW ' + d.toString('hex') + ' ' + JSON.stringify(d.toString('utf-8')))
|
|
27
|
-
})
|
|
28
|
-
// Log the buffer after every change.
|
|
29
|
-
ed.store.subscribe(() => {
|
|
30
|
-
const s = ed.store.getSnapshot().edit
|
|
31
|
-
w('BUF ' + JSON.stringify(s.buf) + ' cur=' + s.cursor)
|
|
32
|
-
})
|
|
33
|
-
setInterval(() => {}, 1 << 30)
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
// Send `ESC d` (what VSCode sends for Ctrl+Delete) to the REAL TuiEditor and
|
|
2
|
-
// print the resulting buffer.
|
|
3
|
-
import { spawn } from 'node:child_process'
|
|
4
|
-
import fs from 'node:fs'
|
|
5
|
-
import path from 'node:path'
|
|
6
|
-
const ESC = String.fromCharCode(27)
|
|
7
|
-
const LOG = '/tmp/ctd-live.log'
|
|
8
|
-
try {
|
|
9
|
-
fs.unlinkSync(LOG)
|
|
10
|
-
} catch {}
|
|
11
|
-
const child = spawn(
|
|
12
|
-
'script',
|
|
13
|
-
['-qec', 'npx tsx scripts/ctd-live.tsx', '/dev/null'],
|
|
14
|
-
{
|
|
15
|
-
cwd: path.resolve('.'),
|
|
16
|
-
env: { ...process.env, COLUMNS: '80', LINES: '24', PROBE_CTD_LOG: LOG },
|
|
17
|
-
},
|
|
18
|
-
)
|
|
19
|
-
child.stdout.on('data', () => {})
|
|
20
|
-
child.stderr.on('data', () => {})
|
|
21
|
-
setTimeout(() => {
|
|
22
|
-
child.stdin.write('foo bar baz')
|
|
23
|
-
setTimeout(() => {
|
|
24
|
-
child.stdin.write(ESC + 'd')
|
|
25
|
-
setTimeout(() => {
|
|
26
|
-
const log = fs.readFileSync(LOG, 'utf-8')
|
|
27
|
-
process.stdout.write(
|
|
28
|
-
log
|
|
29
|
-
.split(String.fromCharCode(10))
|
|
30
|
-
.filter((l) => l.startsWith('RAW') || l.startsWith('BUF'))
|
|
31
|
-
.join(String.fromCharCode(10)) + String.fromCharCode(10),
|
|
32
|
-
)
|
|
33
|
-
child.kill('SIGTERM')
|
|
34
|
-
}, 500)
|
|
35
|
-
}, 400)
|
|
36
|
-
}, 5000)
|
|
37
|
-
child.on('close', () => process.exit(0))
|
package/scripts/eslint-ts6.mts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Run ESLint so that typescript-eslint uses the TypeScript 6 API.
|
|
2
|
-
//
|
|
3
|
-
// The project is built with TypeScript 7, but typescript-eslint (8.x) does
|
|
4
|
-
// not support the TS 7 API yet and refuses to start. Its own recommendation is
|
|
5
|
-
// to run "side by side with TypeScript 6": load the TS 6 compiler just for the
|
|
6
|
-
// linter. We do that by aliasing the `typescript` module to the `typescript-6`
|
|
7
|
-
// package for the duration of the ESLint process, without touching the
|
|
8
|
-
// compiler the project actually builds with.
|
|
9
|
-
import { createRequire } from 'module'
|
|
10
|
-
import Module from 'module'
|
|
11
|
-
import path from 'path'
|
|
12
|
-
import { fileURLToPath } from 'url'
|
|
13
|
-
|
|
14
|
-
// Module._resolveFilename is a private Node field (not in @types/node), so it
|
|
15
|
-
// is reached through a small structural type instead of `any`.
|
|
16
|
-
type ModuleWithResolve = typeof Module & {
|
|
17
|
-
_resolveFilename: (request: string, ...rest: unknown[]) => string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const require = createRequire(import.meta.url)
|
|
21
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
22
|
-
const ts6 = path.join(here, '..', 'node_modules', 'typescript-6')
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
require.resolve(path.join(ts6, 'lib', 'typescript.js'))
|
|
26
|
-
} catch {
|
|
27
|
-
console.error(
|
|
28
|
-
'eslint-ts6: the typescript-6 package is missing. Run: npm i -D typescript-6@npm:typescript@6',
|
|
29
|
-
)
|
|
30
|
-
process.exit(1)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Patch Module._resolveFilename so any require('typescript') resolves to the
|
|
34
|
-
// TS 6 package instead of the TS 7 the project depends on.
|
|
35
|
-
const M = Module as ModuleWithResolve
|
|
36
|
-
const origResolve = M._resolveFilename
|
|
37
|
-
M._resolveFilename = (request: string, ...rest: unknown[]) => {
|
|
38
|
-
if (request === 'typescript') {
|
|
39
|
-
try {
|
|
40
|
-
return require.resolve(path.join(ts6, 'lib', 'typescript.js'))
|
|
41
|
-
} catch {
|
|
42
|
-
return origResolve(request, ...rest)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return origResolve(request, ...rest)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const { ESLint } = await import('eslint')
|
|
49
|
-
const eslint = new ESLint({ fix: process.argv.includes('--fix') })
|
|
50
|
-
const results = await eslint.lintFiles(['.'])
|
|
51
|
-
if (process.argv.includes('--fix')) {
|
|
52
|
-
await ESLint.outputFixes(results)
|
|
53
|
-
}
|
|
54
|
-
const formatter = await eslint.loadFormatter('stylish')
|
|
55
|
-
const out = await formatter.format(results)
|
|
56
|
-
if (out) process.stdout.write(out)
|
|
57
|
-
const errors = results.reduce((n, r) => n + r.errorCount, 0)
|
|
58
|
-
process.exit(errors > 0 ? 1 : 0)
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { Term } from '../test/vt100.mts'
|
|
4
|
-
const NL = String.fromCharCode(10)
|
|
5
|
-
const term = new Term(12, 60)
|
|
6
|
-
const child = spawn(
|
|
7
|
-
'script',
|
|
8
|
-
['-qec', 'npx tsx scripts/inline-status.tsx', '/dev/null'],
|
|
9
|
-
{
|
|
10
|
-
cwd: path.resolve('.'),
|
|
11
|
-
env: { ...process.env, COLUMNS: '60', LINES: '12' },
|
|
12
|
-
},
|
|
13
|
-
)
|
|
14
|
-
child.stdout.on('data', (d) => term.write(d.toString('utf-8')))
|
|
15
|
-
child.stderr.on('data', (d) => term.write(d.toString('utf-8')))
|
|
16
|
-
setTimeout(() => {
|
|
17
|
-
const g = term.grid
|
|
18
|
-
if (g[term.r]) g[term.r][term.c] = String.fromCharCode(9673)
|
|
19
|
-
process.stdout.write(
|
|
20
|
-
g
|
|
21
|
-
.map((r) => r.join('').replace(/\s+$/, ''))
|
|
22
|
-
.map((l, n) => String(n).padStart(2) + '| ' + l)
|
|
23
|
-
.join(NL) +
|
|
24
|
-
NL +
|
|
25
|
-
'CURSOR row=' +
|
|
26
|
-
term.r +
|
|
27
|
-
NL,
|
|
28
|
-
)
|
|
29
|
-
child.kill('SIGTERM')
|
|
30
|
-
}, 5000)
|
|
31
|
-
child.on('close', () => process.exit(0))
|