zames_pro 2.75.6 → 2.77.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 +46 -0
- package/README.md +9 -6
- package/dist/answer-parse.js +19 -3
- package/dist/changelog.js +6 -0
- package/dist/commands.js +50 -5
- package/dist/config.js +7 -0
- package/dist/context.js +104 -1
- package/dist/diagnostics.js +8 -1
- package/dist/goal.js +13 -4
- package/dist/i18n/deepseek.js +13 -0
- package/dist/i18n/help.js +54 -0
- package/dist/i18n/self-review.js +2 -2
- package/dist/index.js +33 -5
- package/dist/input.js +7 -1
- package/dist/mcp.js +9 -1
- package/dist/net-capture.js +37 -0
- package/dist/qwen-browser.js +6 -3
- package/dist/qwen-chats.js +9 -2
- package/dist/run-task.js +8 -2
- package/dist/scheduler.js +28 -3
- package/dist/shell.js +10 -2
- package/dist/subagent.js +17 -4
- package/dist/tool-summary.js +14 -1
- package/dist/tui/ConfigMenu.js +11 -7
- package/dist/tui/ConfirmDialog.js +5 -3
- package/dist/tui/SelectList.js +6 -4
- package/dist/tui/TuiEditor.js +4 -0
- package/dist/tui/edit.js +13 -22
- package/dist/tui/keys.js +12 -12
- package/dist/tui/useMouseWheel.js +9 -0
- package/dist/web.js +95 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.77.0] - 2026-10-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **scheduler:** start the first /loop tick immediately
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **diff:** show no-changes text instead of the git runner sentinel
|
|
19
|
+
- **config:** refuse prototype-polluting segments in setByPath
|
|
20
|
+
- **net:** read the context counter from history_messages and OpenAI usage
|
|
21
|
+
- **agent-loop:** do not flag a report that explains the work as unfinished
|
|
22
|
+
- **agent-loop:** stop treating prose that mentions invoke/parameter as a tool call
|
|
23
|
+
- **backlog:** ignore headings inside fenced code blocks
|
|
24
|
+
- **subagent:** keep the SubagentStop hook output when the run fails
|
|
25
|
+
- **goal:** parse a markdown-formatted evaluator verdict
|
|
26
|
+
- **qwen:** skip the thinking phase when restoring a chat from content_list
|
|
27
|
+
- **changelog:** keep drafted group headings on their own line
|
|
28
|
+
- **tui:** decode modified and release wheel reports correctly
|
|
29
|
+
- **shell:** normalize an invalid Bash timeout instead of crashing the tool
|
|
30
|
+
- **cost:** keep per-tool stats safe for __proto__/constructor tool names
|
|
31
|
+
- **tui:** mark blocked/aborted tool calls as errors in the result icon
|
|
32
|
+
- **mcp:** do not split a surrogate pair when truncating an MCP result
|
|
33
|
+
- **context:** discover skills at the documented 3 levels deep
|
|
34
|
+
- **commands:** /diffstat accepts --staged like /diff
|
|
35
|
+
- **commands:** /add-dir expands a leading ~ to the home directory
|
|
36
|
+
- **scheduler:** accept the N/step cron form
|
|
37
|
+
- **scheduler:** reject a /loop interval that overflows the 32-bit timer
|
|
38
|
+
- **diagnostics:** never cut the check output inside a surrogate pair
|
|
39
|
+
- **web:** close two SSRF-guard bypasses in WebFetch
|
|
40
|
+
- **scheduler:** flag a job whose next fire could not be computed
|
|
41
|
+
- **subagent:** cap a report without splitting a surrogate pair
|
|
42
|
+
- **qwen:** a retry wait cut short by Esc must stop the retry chain
|
|
43
|
+
- **tui:** a bracketed paste split across reads must not leak its end marker
|
|
44
|
+
- **agent-loop:** do not cut a tool result inside a surrogate pair
|
|
45
|
+
- **web:** a broken numeric HTML entity must not fail the whole WebFetch
|
|
46
|
+
- **scheduler:** keep a scheduled message's jobId through the queue merge; explain an unrecognized /loop interval
|
|
47
|
+
- **tui:** localize the Ink dialog key-hint bars
|
|
48
|
+
- **i18n:** drop the dead /resume <n> hint from /sessions and the docs
|
|
49
|
+
|
|
50
|
+
## [2.76.0] - 2026-10-16
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- bake methodology skills in, localize skill hints, fix Ctrl+Del and scroll-to-bottom
|
|
55
|
+
|
|
10
56
|
## [2.75.6] - 2026-10-16
|
|
11
57
|
|
|
12
58
|
### Added
|
package/README.md
CHANGED
|
@@ -374,8 +374,8 @@ Codex CLI:
|
|
|
374
374
|
runtime.
|
|
375
375
|
- /add-dir <path> — validate an extra directory (the sandbox is fixed at
|
|
376
376
|
startup; relaunch with --dir to write there).
|
|
377
|
-
- /resume <
|
|
378
|
-
dialogue into the terminal, so the restored context is visible. Only the
|
|
377
|
+
- /resume-id <id> (or picking a chat in the /chats list) — open a chat and
|
|
378
|
+
PRINT its dialogue into the terminal, so the restored context is visible. Only the
|
|
379
379
|
last 20 messages are shown (`RESTORED_HISTORY_LIMIT`). /last reopens the last
|
|
380
380
|
chat of the current directory with no lookup step; /sessions shows the saved
|
|
381
381
|
sessions with their relative age.
|
|
@@ -400,7 +400,9 @@ Codex CLI:
|
|
|
400
400
|
tests`). /cron "<min> <hour> <dom> <month> <dow>" <task> — run on a schedule.
|
|
401
401
|
/jobs [rm <id>|clear] lists and stops them. A fired job is put into the same
|
|
402
402
|
message queue you type into, so it runs when the agent is free (never mid-
|
|
403
|
-
generation) and still respects the send throttle.
|
|
403
|
+
generation) and still respects the send throttle. The FIRST run of a `/loop`
|
|
404
|
+
is due at once (and runs as soon as the agent is idle); a `/cron` job waits
|
|
405
|
+
for its schedule.
|
|
404
406
|
- /thinking [on|off] and /web [on|off] — toggle the reasoning / web-search
|
|
405
407
|
behaviour. On **DeepSeek** these are the "Deep thinking" / "Smart search"
|
|
406
408
|
toggles; on **Qwen** they drive its thinking MODE (Thinking / Fast) and the
|
|
@@ -959,8 +961,8 @@ zames --task "..." --output-format jsonl \
|
|
|
959
961
|
модель Qwen на лету.
|
|
960
962
|
- /add-dir <path> — проверить дополнительную директорию (песочница
|
|
961
963
|
фиксируется при старте; для записи туда перезапустите с --dir).
|
|
962
|
-
- /resume <
|
|
963
|
-
диалог в терминал, чтобы восстановленный контекст был виден. Показываются
|
|
964
|
+
- /resume-id <id> (или выбор чата в списке /chats) — открыть чат и
|
|
965
|
+
НАПЕЧАТАТЬ его диалог в терминал, чтобы восстановленный контекст был виден. Показываются
|
|
964
966
|
только последние 20 сообщений (`RESTORED_HISTORY_LIMIT`). /last открывает
|
|
965
967
|
последний чат текущей директории без поиска; /sessions показывает сохранённые
|
|
966
968
|
сессии с их относительным возрастом.
|
|
@@ -987,7 +989,8 @@ zames --task "..." --output-format jsonl \
|
|
|
987
989
|
запуск по расписанию. /jobs [rm <id>|clear] перечисляет и останавливает их.
|
|
988
990
|
Сработавшая задача попадает в ту же очередь сообщений, что и ваш ввод, так
|
|
989
991
|
что выполняется, когда агент свободен (никогда посреди генерации), и всё
|
|
990
|
-
равно соблюдает троттлинг отправки.
|
|
992
|
+
равно соблюдает троттлинг отправки. ПЕРВЫЙ запуск `/loop` — сразу же (как
|
|
993
|
+
только агент свободен); задача `/cron` ждёт своего расписания.
|
|
991
994
|
- /thinking [on|off] и /web [on|off] — переключить режим размышления /
|
|
992
995
|
веб-поиска. В **DeepSeek** это тумблеры «Deep thinking» / «Smart search»;
|
|
993
996
|
в **Qwen** — это режим размышления (Thinking / Fast) и пункт «Web search»
|
package/dist/answer-parse.js
CHANGED
|
@@ -25,6 +25,14 @@ export function truncateToolResult(text, limit) {
|
|
|
25
25
|
// lines — a cut that EXCLUDED the newline left a half line before the marker.
|
|
26
26
|
if (nl >= searchFrom)
|
|
27
27
|
cut = nl + 1;
|
|
28
|
+
// Never split a surrogate PAIR (an emoji / astral CJK char): the high half
|
|
29
|
+
// alone is a lone surrogate — a broken code point some channels replace with
|
|
30
|
+
// '?' or truncate at. Step back one unit so the pair is dropped WHOLE.
|
|
31
|
+
else {
|
|
32
|
+
const c = text.charCodeAt(cut - 1);
|
|
33
|
+
if (c >= 0xd800 && c <= 0xdbff)
|
|
34
|
+
cut--;
|
|
35
|
+
}
|
|
28
36
|
const omitted = text.length - cut;
|
|
29
37
|
return text.slice(0, cut) + NL + `[...truncated ${omitted} chars]`;
|
|
30
38
|
}
|
|
@@ -37,8 +45,13 @@ export function responseLooksLikeToolCall(rawResponse) {
|
|
|
37
45
|
const raw = rawResponse || '';
|
|
38
46
|
return (
|
|
39
47
|
// Explicit tool-call format markers: the JSON key "tool", XML/DSML tags,
|
|
40
|
-
// function_call, etc.
|
|
41
|
-
|
|
48
|
+
// function_call, etc. The BARE words `invoke`/`parameter` are deliberately
|
|
49
|
+
// NOT markers on their own: an ordinary final report can use them as
|
|
50
|
+
// English words ("a timeout parameter: …", "the invoke helper …"), and a
|
|
51
|
+
// false positive rejected a perfectly good answer and re-asked the model
|
|
52
|
+
// until the retry budget ended the task with an error. A real XML/DSML
|
|
53
|
+
// call is still caught by the tag pattern below.
|
|
54
|
+
/("tool"\s*:|\btool_calls?\b|DSML|function_call)/i.test(raw) ||
|
|
42
55
|
// "tool" without an opening quote/bracket, with a junk prefix
|
|
43
56
|
// (`<|tool":`, `**tool**:`, `- tool:`): a call key, not prose.
|
|
44
57
|
/(^|[^A-Za-z0-9_])(?:\*\*)?tool(?:\*\*)?["'`\u2018\u2019\u201c\u201d]*\s*:/.test(raw) ||
|
|
@@ -98,7 +111,10 @@ export function looksLikeUnfinishedWork(text) {
|
|
|
98
111
|
if (/\b(done|finished|completed|готово|выполнено|завершено)\b/i.test(t)) {
|
|
99
112
|
return false;
|
|
100
113
|
}
|
|
101
|
-
|
|
114
|
+
// `update`/`summarize`/`explain` are REPORT verbs (`I will update you on the
|
|
115
|
+
// results: …`) — they do not promise a tool call. A colon anywhere in the
|
|
116
|
+
// gap also means the sentence is explaining a fact, not about to act.
|
|
117
|
+
const en = /\b(now|next|then|let me|let's|i will|i'll|i am going to|i'm going to|going to|about to|will now|time to)\b[^.!?\n:]{0,120}\b(write|edit|read|run|check|add|fix|create|remove|delete|apply|test|commit|push|install|open|search|look|verify|change|modify|implement|review)\b/i;
|
|
102
118
|
const ru = /(^|[^а-яё])(проверю|обновлю|исправлю|добавлю|запущу|выполню|посмотрю|прочитаю|изменю|попробую|сделаю)([^а-яё]|$)/i;
|
|
103
119
|
return en.test(t) || ru.test(t);
|
|
104
120
|
}
|
package/dist/changelog.js
CHANGED
|
@@ -99,6 +99,12 @@ export function syncUnreleased(changelog, body) {
|
|
|
99
99
|
const tail = next < 0 ? '' : rest.slice(next);
|
|
100
100
|
const cleanBody = String(body || '')
|
|
101
101
|
.replace(/^## \[Unreleased\]\s*/m, '')
|
|
102
|
+
// A drafted group heading must START a line of its own. When the draft
|
|
103
|
+
// carries several groups (`## Added` … `## Fixed`) without a blank line
|
|
104
|
+
// between them, the next group used to stay glued to the last bullet
|
|
105
|
+
// (`- a### Fixed`) — it was not a heading at all, so the item vanished
|
|
106
|
+
// from the rendered changelog.
|
|
107
|
+
.replace(/([^\n])\n?(?=## )/g, '$1' + nl + nl)
|
|
102
108
|
.trim()
|
|
103
109
|
// The draft renders groups as `## Added` (top-level), but INSIDE the file
|
|
104
110
|
// a version's groups are `### Added` (a subsection of the version heading).
|
package/dist/commands.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { providerLabel } from './providers.js';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import { isImageName } from './attachments.js';
|
|
4
5
|
import { translate } from './i18n.js';
|
|
5
6
|
// Helpers for the extra slash commands (/diff, /cost, /export, /doctor,
|
|
@@ -14,8 +15,11 @@ export function formatDiff(diffText, opts = {}, t = translate('en')) {
|
|
|
14
15
|
.split(CR + NL)
|
|
15
16
|
.join(NL)
|
|
16
17
|
.trimEnd();
|
|
17
|
-
|
|
18
|
+
// The shared git runner reports an EMPTY result as this sentinel, so a clean
|
|
19
|
+
// tree must not print it to the operator (formatDiffStat already handles it).
|
|
20
|
+
if (!trimmed || trimmed.trim() === '(command produced no output)') {
|
|
18
21
|
return t('diff.no_changes');
|
|
22
|
+
}
|
|
19
23
|
const lines = trimmed.split(NL);
|
|
20
24
|
if (lines.length <= maxLines)
|
|
21
25
|
return trimmed;
|
|
@@ -28,6 +32,12 @@ export function formatDiff(diffText, opts = {}, t = translate('en')) {
|
|
|
28
32
|
export function diffGitArgs(staged = false) {
|
|
29
33
|
return staged ? ['diff', '--staged'] : ['diff'];
|
|
30
34
|
}
|
|
35
|
+
// Git arguments for `/diffstat`. Same shape as diffGitArgs() so the two
|
|
36
|
+
// commands stay symmetric: `/diffstat --staged` used to be impossible (the
|
|
37
|
+
// command hardcoded `git diff --stat`), even though `/diff --staged` worked.
|
|
38
|
+
export function diffStatGitArgs(staged = false) {
|
|
39
|
+
return staged ? ['diff', '--staged', '--stat'] : ['diff', '--stat'];
|
|
40
|
+
}
|
|
31
41
|
// ---------- /diffstat ----------
|
|
32
42
|
/** Render `git diff --stat` output (a compact change summary) for display. */
|
|
33
43
|
export function formatDiffStat(statText, opts = {}, t = translate('en')) {
|
|
@@ -84,9 +94,15 @@ export function summarizeTranscript(entries) {
|
|
|
84
94
|
const stats = {
|
|
85
95
|
turns: 0,
|
|
86
96
|
toolCalls: 0,
|
|
87
|
-
|
|
97
|
+
// Null-prototype maps: a tool NAME is arbitrary text (an MCP server can
|
|
98
|
+
// advertise anything, the model may emit any name). With a plain {} a name
|
|
99
|
+
// like '__proto__' assigns the PROTOTYPE (the tool vanishes from the count
|
|
100
|
+
// and Object.prototype gets mutated), and 'constructor'/'toString' read an
|
|
101
|
+
// inherited function instead of a number — /cost then printed
|
|
102
|
+
// "constructor: function Object() { [native code] }1".
|
|
103
|
+
toolCounts: Object.create(null),
|
|
88
104
|
toolMs: 0,
|
|
89
|
-
toolTime:
|
|
105
|
+
toolTime: Object.create(null),
|
|
90
106
|
durationMs: 0,
|
|
91
107
|
startedAt: null,
|
|
92
108
|
autoCompacts: 0,
|
|
@@ -332,9 +348,18 @@ export function renderDoctor(d, t = translate('en')) {
|
|
|
332
348
|
}
|
|
333
349
|
// ---------- /add-dir ----------
|
|
334
350
|
export function resolveExtraDir(input, workdir, t = translate('en')) {
|
|
335
|
-
|
|
351
|
+
let raw = String(input ?? '').trim();
|
|
336
352
|
if (!raw)
|
|
337
353
|
return { error: t('adddir.usage') };
|
|
354
|
+
// A leading `~` means the home directory (a shell convention the operator
|
|
355
|
+
// expects). Without expansion resolve() treated it as a literal directory
|
|
356
|
+
// named `~` under the working dir, so /add-dir ~ reported "No such
|
|
357
|
+
// directory: /w/~". Only a LEADING `~` / `~/` expands; a `~` elsewhere is
|
|
358
|
+
// an ordinary character.
|
|
359
|
+
if (raw === '~')
|
|
360
|
+
raw = os.homedir();
|
|
361
|
+
else if (raw.startsWith('~/') || raw.startsWith('~' + path.sep))
|
|
362
|
+
raw = path.join(os.homedir(), raw.slice(2));
|
|
338
363
|
const abs = path.resolve(workdir, raw);
|
|
339
364
|
if (abs === path.resolve(workdir)) {
|
|
340
365
|
return { error: t('adddir.same') };
|
|
@@ -828,6 +853,7 @@ export function mergeMessages(msgs) {
|
|
|
828
853
|
return {
|
|
829
854
|
text: list[0].text,
|
|
830
855
|
attachments: (list[0].attachments || []).slice(),
|
|
856
|
+
jobId: list[0].jobId,
|
|
831
857
|
};
|
|
832
858
|
}
|
|
833
859
|
let imgOffset = 0;
|
|
@@ -861,7 +887,10 @@ export function mergeMessages(msgs) {
|
|
|
861
887
|
' messages while you were working. They are listed below in order — ' +
|
|
862
888
|
'treat them as one instruction.';
|
|
863
889
|
const text = header + NL + NL + parts.join(NL + NL + '---' + NL + NL);
|
|
864
|
-
|
|
890
|
+
// Keep the FIRST scheduled job id in the batch so the ticker still knows a
|
|
891
|
+
// copy of that job is in flight (see MergedMessage.jobId).
|
|
892
|
+
const jobId = list.find((m) => m.jobId !== undefined)?.jobId;
|
|
893
|
+
return { text, attachments, jobId };
|
|
865
894
|
}
|
|
866
895
|
// Parse BACKLOG.md into actionable items. Recognizes headings of the form
|
|
867
896
|
// `### A1. ...`, `### D7. ...` and marks an item done when its heading carries
|
|
@@ -870,8 +899,24 @@ export function mergeMessages(msgs) {
|
|
|
870
899
|
export function parseBacklogItems(text) {
|
|
871
900
|
const out = [];
|
|
872
901
|
let priority = '';
|
|
902
|
+
// A fenced code block in an item's body may contain a line that LOOKS like a
|
|
903
|
+
// heading (an example of the format, a pasted log). Those are not headings:
|
|
904
|
+
// counting them invented a phantom P0 item that `/improve` picked over the
|
|
905
|
+
// real backlog. Track the fence (``` or ~~~, at least three) and skip.
|
|
906
|
+
let fence = '';
|
|
873
907
|
for (const raw of String(text ?? '').split(NL)) {
|
|
874
908
|
const line = raw.trimEnd();
|
|
909
|
+
const fenceMatch = /^\s*(`{3,}|~{3,})/.exec(line);
|
|
910
|
+
if (fenceMatch) {
|
|
911
|
+
const mark = fenceMatch[1][0];
|
|
912
|
+
if (!fence)
|
|
913
|
+
fence = mark;
|
|
914
|
+
else if (mark === fence)
|
|
915
|
+
fence = '';
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (fence)
|
|
919
|
+
continue;
|
|
875
920
|
const p = /^##\s+(P[0-3])\b/.exec(line);
|
|
876
921
|
if (p) {
|
|
877
922
|
priority = p[1];
|
package/dist/config.js
CHANGED
|
@@ -498,8 +498,15 @@ export function getByPath(obj, path) {
|
|
|
498
498
|
}
|
|
499
499
|
return cur;
|
|
500
500
|
}
|
|
501
|
+
// Object keys that must NEVER be walked/assigned by a dotted path: they reach
|
|
502
|
+
// the PROTOTYPE chain. deepMerge() already skips them; setByPath did not, so a
|
|
503
|
+
// `__proto__.x` segment polluted Object.prototype for the whole process (and
|
|
504
|
+
// wrote the injected keys into the config file).
|
|
505
|
+
const UNSAFE_PATH_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
501
506
|
export function setByPath(obj, path, value) {
|
|
502
507
|
const parts = path.split('.');
|
|
508
|
+
if (parts.some((p) => UNSAFE_PATH_KEYS.has(p)))
|
|
509
|
+
return;
|
|
503
510
|
let cur = obj;
|
|
504
511
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
505
512
|
const p = parts[i];
|
package/dist/context.js
CHANGED
|
@@ -57,6 +57,105 @@ const BUILTIN_SKILLS = [
|
|
|
57
57
|
'couplings, and any WHY-comments that matter.\n' +
|
|
58
58
|
'4. Do NOT change code. Answer in the operator language.',
|
|
59
59
|
},
|
|
60
|
+
// Methodology skills, ported from superpowers (github.com/obra/superpowers)
|
|
61
|
+
// as plain prompt bodies — the zames equivalent of its SessionStart
|
|
62
|
+
// bootstrap, without a hook. The model is nudged to read the matching one
|
|
63
|
+
// before a non-trivial task by SKILLS_DISCIPLINE (src/system-prompt.ts).
|
|
64
|
+
// Baked in (not a file on disk) so a user who installed the npm package has
|
|
65
|
+
// them out of the box; a project skill of the same name overrides them.
|
|
66
|
+
{
|
|
67
|
+
name: 'brainstorming',
|
|
68
|
+
description: 'Use when the request is a GOAL, not a spec — "add X", "make it better", "integrate Y" — and it is unclear what "done" means.',
|
|
69
|
+
body: 'When a request is a goal ("make scrolling feel right"), building ' +
|
|
70
|
+
'immediately produces the wrong thing fast. First make the intent concrete.\n' +
|
|
71
|
+
'Pin down: (a) the REAL outcome — what should be TRUE after that is not ' +
|
|
72
|
+
'true now; (b) the acceptance check — how the operator will know it works; ' +
|
|
73
|
+
'(c) scope edges — what is explicitly NOT part of this; (d) the ' +
|
|
74
|
+
'alternatives considered and why you chose one.\n' +
|
|
75
|
+
'zames has NO conversational turn: the operator sees only your final ' +
|
|
76
|
+
'respond. So do the brainstorming YOURSELF and return a SHORT, concrete ' +
|
|
77
|
+
'proposal in ONE respond — the goal as you understood it, your ' +
|
|
78
|
+
'assumptions and defaults, what you will NOT do — then either proceed if ' +
|
|
79
|
+
'the request was clearly actionable, or stop and ask the ONE decision you ' +
|
|
80
|
+
'genuinely cannot make alone. For an already-precise request ("read X", ' +
|
|
81
|
+
'"rename Y") skip this entirely.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'writing-plans',
|
|
85
|
+
description: 'Use when a task needs more than one or two tool calls — a multi-file change, a new feature, a refactor. Breaks it into small ordered steps with exact paths and a verification for each.',
|
|
86
|
+
body: 'A plan is a sequence of steps a careful engineer with NO project ' +
|
|
87
|
+
'context could follow. Each step (2-5 minutes) names: the EXACT file ' +
|
|
88
|
+
'path(s); the concrete edit; how to verify it (command, test name, ' +
|
|
89
|
+
'expected output). Order matters: put the step that DE-RISKS the task ' +
|
|
90
|
+
'first (a spike, a failing test, a proof the API works) — do not build ' +
|
|
91
|
+
'three layers on an unverified assumption.\n' +
|
|
92
|
+
'Rules: one concern per step ("add the flag AND refactor the caller" is ' +
|
|
93
|
+
'two); prefer a step that FAILS LOUDLY over one that silently ' +
|
|
94
|
+
'half-works; every behavioural change gets a test step (see ' +
|
|
95
|
+
'test-driven-development); if a step turns out wrong, STOP and revise the ' +
|
|
96
|
+
'plan instead of improvising; a 30-step plan means the task should be ' +
|
|
97
|
+
'split. In zames the plan lives in your reasoning and, for a long task, ' +
|
|
98
|
+
'in a TodoWrite list — one entry per step. Do not narrate it between tool ' +
|
|
99
|
+
'calls; the todo list IS the visible plan.',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'test-driven-development',
|
|
103
|
+
description: 'Use when writing or changing testable behaviour — a bug fix, a new function, a parser, a formatter. Write the failing test first, watch it fail, then make it pass.',
|
|
104
|
+
body: 'RED -> GREEN -> REFACTOR. The order is the point, not a formality.\n' +
|
|
105
|
+
'1. RED — write the smallest test that expresses the new behaviour and ' +
|
|
106
|
+
'RUN it. It must FAIL for the RIGHT reason ("not implemented", not a ' +
|
|
107
|
+
'typo). A test you never saw fail proves nothing.\n' +
|
|
108
|
+
'2. GREEN — write the MINIMUM code that makes it pass; do not add the ' +
|
|
109
|
+
'next feature "while you are there".\n' +
|
|
110
|
+
'3. REFACTOR — clean up with the test green, then re-run.\n' +
|
|
111
|
+
'A good test asserts BEHAVIOUR (input -> output), is deterministic (no ' +
|
|
112
|
+
'sleeps, no network, no wall-clock or test-order dependence) and fails ' +
|
|
113
|
+
'for ONE reason with a message that says what broke. In this project a ' +
|
|
114
|
+
'bug fix ALWAYS gets a regression test reproducing the original bug. ' +
|
|
115
|
+
'When a test needs fixing, ask whether the TEST or the CODE was wrong; ' +
|
|
116
|
+
'if the code was written before its test, rewrite it driven by the test.',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'systematic-debugging',
|
|
120
|
+
description: 'Use when something is broken and the cause is not obvious — a failing test, a wrong result, a hang, a crash. Four phases: reproduce, isolate, hypothesize, fix.',
|
|
121
|
+
body: 'Do NOT start by editing code. Work the four phases in order; each ' +
|
|
122
|
+
'produces an artifact you can point at.\n' +
|
|
123
|
+
'1. REPRODUCE — get a command that FAILS the way the operator describes; ' +
|
|
124
|
+
'copy the exact error. If it passes you do not have the bug yet: narrow ' +
|
|
125
|
+
'the input until it fails.\n' +
|
|
126
|
+
'2. ISOLATE — shrink to the smallest failing unit (bisect, stub, ' +
|
|
127
|
+
'binary-search). Read the code the failing path ACTUALLY executes. Check ' +
|
|
128
|
+
'the obvious first: wrong file, stale build, cached state, different ' +
|
|
129
|
+
'config, an error swallowed by a catch.\n' +
|
|
130
|
+
'3. HYPOTHESIZE — state it in one sentence and predict what you should ' +
|
|
131
|
+
'see if it is right; test exactly that. One change at a time; prefer an ' +
|
|
132
|
+
'observation (log, test assertion) over an edit. If the prediction is ' +
|
|
133
|
+
'wrong, DISCARD the hypothesis.\n' +
|
|
134
|
+
'4. FIX AND VERIFY — fix the CAUSE, not the symptom; add a regression ' +
|
|
135
|
+
'test that fails WITHOUT the fix; re-run the phase-1 reproduction ' +
|
|
136
|
+
'verbatim; look for the same bug elsewhere.\n' +
|
|
137
|
+
'Anti-patterns: editing before reproducing; "it works now" without ' +
|
|
138
|
+
'knowing what changed; silencing an error you do not understand with a ' +
|
|
139
|
+
'try/catch; trusting a green test never seen red.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'verification-before-completion',
|
|
143
|
+
description: 'Use BEFORE declaring a task done — a feature, a bug fix, a generated file. Forces you to run the verification you promised instead of trusting the code looks right.',
|
|
144
|
+
body: '"It should work" is not done. Before reporting success, produce ' +
|
|
145
|
+
'EVIDENCE from the real artifact, not from your intent.\n' +
|
|
146
|
+
'1. Re-read the original request; tick every requirement (including the ' +
|
|
147
|
+
'ones in a parenthetical) against something you can show.\n' +
|
|
148
|
+
'2. Run the thing: build/typecheck/tests for code; run a script; for a ' +
|
|
149
|
+
'generated file, open/parse it and confirm it is COMPLETE, not ' +
|
|
150
|
+
'truncated; for a UI, the real screen, not the diff.\n' +
|
|
151
|
+
'3. Re-read your own diff as a reviewer: leftover debug output, dead ' +
|
|
152
|
+
'code, a wrong path, a test that asserts nothing.\n' +
|
|
153
|
+
'4. State the evidence in the final report — the exact command and its ' +
|
|
154
|
+
'result — so the operator can reproduce it. "Tests pass" without the ' +
|
|
155
|
+
'command is not evidence.\n' +
|
|
156
|
+
'If you cannot run the verification, SAY SO explicitly and describe what ' +
|
|
157
|
+
'you did instead; never report "done" implying a check you did not run.',
|
|
158
|
+
},
|
|
60
159
|
];
|
|
61
160
|
// Per-file budgets. AGENTS.md/MEMORY.md are hand-maintained and grow over
|
|
62
161
|
// time, so the caps are generous: an AGENTS.md of a real project (this repo's
|
|
@@ -241,7 +340,11 @@ async function findSkillFiles(root, depth = 3) {
|
|
|
241
340
|
await walk(full, level + 1);
|
|
242
341
|
}
|
|
243
342
|
};
|
|
244
|
-
|
|
343
|
+
// Start at level 0 so `level < depth` admits exactly `depth` levels of
|
|
344
|
+
// subdirectories: with walk(root, 1) the FIRST level counted as 1 and the
|
|
345
|
+
// walk stopped one level early, so a skill at the documented "3 levels
|
|
346
|
+
// deep" (`.zames/skills/<a>/<b>/<skill>/SKILL.md`) was never discovered.
|
|
347
|
+
await walk(root, 0);
|
|
245
348
|
return out;
|
|
246
349
|
}
|
|
247
350
|
export async function loadSkills(workdir) {
|
package/dist/diagnostics.js
CHANGED
|
@@ -35,7 +35,14 @@ export function summarizeDiagnostics(raw, maxChars) {
|
|
|
35
35
|
const chosen = errLines.length ? errLines : lines;
|
|
36
36
|
let out = chosen.join('\n');
|
|
37
37
|
if (out.length > maxChars) {
|
|
38
|
-
|
|
38
|
+
// Never split a surrogate PAIR (emoji / astral CJK): the high half alone
|
|
39
|
+
// is a lone surrogate in the model's context. Same rule as
|
|
40
|
+
// truncateToolResult (agent-loop) and capReport (subagent).
|
|
41
|
+
let cut = maxChars;
|
|
42
|
+
const c = out.charCodeAt(cut - 1);
|
|
43
|
+
if (c >= 0xd800 && c <= 0xdbff)
|
|
44
|
+
cut--;
|
|
45
|
+
out = out.slice(0, cut) + '\n… [diagnostics truncated]';
|
|
39
46
|
}
|
|
40
47
|
return out;
|
|
41
48
|
}
|
package/dist/goal.js
CHANGED
|
@@ -70,12 +70,21 @@ function argumentReason(reason) {
|
|
|
70
70
|
// "continue", never as "done").
|
|
71
71
|
export function parseGoalVerdict(text) {
|
|
72
72
|
const t = String(text ?? '');
|
|
73
|
-
|
|
73
|
+
// The label may be bolded/italicised and the colon may carry surrounding
|
|
74
|
+
// spaces (`**VERDICT:** MET`) — a markdown-model answer did exactly that and
|
|
75
|
+
// the strict `VERDICT:` matcher returned null, so the goal never parsed and
|
|
76
|
+
// the loop auto-continued until the turn budget.
|
|
77
|
+
const m = /VERDICT\s*[*_`]*\s*:\s*[*_`]*\s*(MET|NOT_MET|IMPOSSIBLE)/i.exec(t);
|
|
74
78
|
if (!m)
|
|
75
79
|
return null;
|
|
76
|
-
const raw = m[
|
|
80
|
+
const raw = m[1].toUpperCase();
|
|
77
81
|
const status = raw === 'MET' ? 'met' : raw === 'IMPOSSIBLE' ? 'impossible' : 'not_met';
|
|
78
|
-
const rm = /REASON
|
|
79
|
-
const reason = rm
|
|
82
|
+
const rm = /REASON\s*[*_`]*\s*:\s*(.+)/i.exec(t);
|
|
83
|
+
const reason = rm
|
|
84
|
+
? rm[1]
|
|
85
|
+
.replace(/`{1,3}/g, '')
|
|
86
|
+
.replace(/\*{1,2}/g, '')
|
|
87
|
+
.trim()
|
|
88
|
+
: '';
|
|
80
89
|
return { status, reason };
|
|
81
90
|
}
|
package/dist/i18n/deepseek.js
CHANGED
|
@@ -114,6 +114,19 @@ export const deepseekMessages = {
|
|
|
114
114
|
'auth.form.hint_field': { ru: 'поле', en: 'field' },
|
|
115
115
|
'auth.form.hint_submit': { ru: 'войти', en: 'sign in' },
|
|
116
116
|
'auth.form.hint_cancel': { ru: 'отмена', en: 'cancel' },
|
|
117
|
+
// Key-hint labels of the Ink dialogs (SelectList / ConfigMenu /
|
|
118
|
+
// ConfirmDialog). They used to be hardcoded Russian, so an English UI
|
|
119
|
+
// still showed a Russian hint bar.
|
|
120
|
+
'tui.hint.select': { ru: 'выбор', en: 'select' },
|
|
121
|
+
'tui.hint.edit': { ru: 'правка', en: 'edit' },
|
|
122
|
+
'tui.hint.ok': { ru: 'ок', en: 'ok' },
|
|
123
|
+
'tui.hint.cancel': { ru: 'отмена', en: 'cancel' },
|
|
124
|
+
'tui.hint.search': { ru: 'поиск', en: 'search' },
|
|
125
|
+
'tui.hint.reset': { ru: 'сброс', en: 'reset' },
|
|
126
|
+
'tui.hint.quit': { ru: 'выход', en: 'quit' },
|
|
127
|
+
'tui.hint.change': { ru: 'изменить', en: 'change' },
|
|
128
|
+
// Marks the CURRENT entry in a list menu (e.g. the active Qwen model).
|
|
129
|
+
'tui.list.current': { ru: '← текущая', en: '← current' },
|
|
117
130
|
'auth.form.note_saved': {
|
|
118
131
|
ru: 'Enter — использовать сохранённые логин и пароль.',
|
|
119
132
|
en: 'Enter — use the saved login and password.',
|
package/dist/i18n/help.js
CHANGED
|
@@ -330,6 +330,10 @@ export const helpMessages = {
|
|
|
330
330
|
ru: 'Использование: /cron "<мин> <час> <день> <мес> <день недели>" <задача>',
|
|
331
331
|
en: 'Usage: /cron "<min> <hour> <dom> <month> <dow>" <task>',
|
|
332
332
|
},
|
|
333
|
+
'sched.interval_typo': {
|
|
334
|
+
ru: 'Интервал «{v}» не распознан — задача НЕ добавлена. Единицы: s/sec, m/min, h (например, /loop 10m проверь тесты). Без единицы — СЕКУНДЫ (120 = 120с), для минут нужен суффикс m.',
|
|
335
|
+
en: 'Interval "{v}" was not recognized — NO job was added. Units: s/sec, m/min, h (e.g. /loop 10m run the tests). Without a unit it means SECONDS (120 = 120s); use the m suffix for minutes.',
|
|
336
|
+
},
|
|
333
337
|
'sched.loop_too_short': {
|
|
334
338
|
ru: 'Слишком часто: минимум {min} (троттлинг отправок 15с).',
|
|
335
339
|
en: 'Too frequent: minimum {min} (the send throttle is 15s).',
|
|
@@ -338,6 +342,10 @@ export const helpMessages = {
|
|
|
338
342
|
ru: '⏰ Задача #{id} добавлена ({when}): {task}',
|
|
339
343
|
en: '⏰ Job #{id} added ({when}): {task}',
|
|
340
344
|
},
|
|
345
|
+
'sched.added_hint': {
|
|
346
|
+
ru: 'Если агент свободен — запуск сразу, иначе сразу после текущей задачи; дальше — по расписанию. Список — /jobs, удалить — /jobs rm <id>.',
|
|
347
|
+
en: 'Runs at once if the agent is idle, otherwise right after the current task; then on schedule. List: /jobs, remove: /jobs rm <id>.',
|
|
348
|
+
},
|
|
341
349
|
'sched.title': { ru: 'Запланированные задачи:', en: 'Scheduled jobs:' },
|
|
342
350
|
'sched.none': { ru: 'Запланированных задач нет.', en: 'No scheduled jobs.' },
|
|
343
351
|
'sched.fired': {
|
|
@@ -574,4 +582,50 @@ export const helpMessages = {
|
|
|
574
582
|
ru: 'Индикаторы в статус-строке: 🧠 — глубокое размышление, 🌐 — поиск в интернете (видны, когда включены).',
|
|
575
583
|
en: 'Status-line icons: 🧠 — Deep thinking, 🌐 — web search (shown when enabled).',
|
|
576
584
|
},
|
|
585
|
+
// Localized descriptions for the WELL-KNOWN skills (built-in + bundled
|
|
586
|
+
// methodology). A skill's SKILL.md frontmatter is agent-facing English, so
|
|
587
|
+
// showing it verbatim in the «/» hints would leave an English line in a
|
|
588
|
+
// Russian UI. Keyed by skill NAME, applied regardless of source, so a local
|
|
589
|
+
// `.zames/skills/` copy of a bundled skill still shows the translated text.
|
|
590
|
+
// An unknown (user-authored) skill has no key and keeps its own description.
|
|
591
|
+
'skill.desc.commit': {
|
|
592
|
+
ru: 'подготовить аккуратный git-коммит из текущих изменений',
|
|
593
|
+
en: 'prepare a clean git commit from the current changes',
|
|
594
|
+
},
|
|
595
|
+
'skill.desc.review': {
|
|
596
|
+
ru: 'ревью текущего диффа на баги и риски (без правок)',
|
|
597
|
+
en: 'review the current diff for bugs and risks (no changes)',
|
|
598
|
+
},
|
|
599
|
+
'skill.desc.test': {
|
|
600
|
+
ru: 'прогнать тесты проекта и чинить падения до зелёного',
|
|
601
|
+
en: 'run the project tests and fix failures until green',
|
|
602
|
+
},
|
|
603
|
+
'skill.desc.debug': {
|
|
604
|
+
ru: 'системно диагностировать и починить баг',
|
|
605
|
+
en: 'systematically diagnose and fix a bug',
|
|
606
|
+
},
|
|
607
|
+
'skill.desc.explain': {
|
|
608
|
+
ru: 'объяснить, как работает файл/модуль/фича',
|
|
609
|
+
en: 'explain how a file/module/feature works',
|
|
610
|
+
},
|
|
611
|
+
'skill.desc.brainstorming': {
|
|
612
|
+
ru: 'когда запрос — это цель, а не спека: сначала прояснить замысел',
|
|
613
|
+
en: 'when the request is a goal, not a spec: pin down the intent first',
|
|
614
|
+
},
|
|
615
|
+
'skill.desc.writing-plans': {
|
|
616
|
+
ru: 'разбить многошаговую задачу на маленькие шаги с проверкой каждого',
|
|
617
|
+
en: 'break a multi-step task into small steps, each with a check',
|
|
618
|
+
},
|
|
619
|
+
'skill.desc.test-driven-development': {
|
|
620
|
+
ru: 'сначала падающий тест, потом код (RED → GREEN → REFACTOR)',
|
|
621
|
+
en: 'write the failing test first, then the code (RED → GREEN → REFACTOR)',
|
|
622
|
+
},
|
|
623
|
+
'skill.desc.systematic-debugging': {
|
|
624
|
+
ru: 'четыре фазы отладки: воспроизвести, изолировать, гипотеза, фикс',
|
|
625
|
+
en: 'four debugging phases: reproduce, isolate, hypothesize, fix',
|
|
626
|
+
},
|
|
627
|
+
'skill.desc.verification-before-completion': {
|
|
628
|
+
ru: 'прогнать проверку ПЕРЕД тем, как объявить задачу готовой',
|
|
629
|
+
en: 'run the verification BEFORE declaring the task done',
|
|
630
|
+
},
|
|
577
631
|
};
|
package/dist/i18n/self-review.js
CHANGED
|
@@ -171,8 +171,8 @@ export const selfReviewMessages = {
|
|
|
171
171
|
en: 'No saved sessions. They appear after the first task/chat.',
|
|
172
172
|
},
|
|
173
173
|
'sessions.restore_hint': {
|
|
174
|
-
ru: 'Восстановить: /resume-id <id>
|
|
175
|
-
en: 'Restore: /resume-id <id> (full id) or
|
|
174
|
+
ru: 'Восстановить: /resume-id <id> (полный id) или выбери сессию в списке /sessions.',
|
|
175
|
+
en: 'Restore: /resume-id <id> (full id) or pick a session from the /sessions list.',
|
|
176
176
|
},
|
|
177
177
|
'sessions.pick_title': {
|
|
178
178
|
ru: 'Выберите сессию (↑/↓, Enter — открыть, Esc — отмена)',
|