zames_pro 2.60.0 → 2.61.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 +31 -1
- package/README.md +2 -10
- package/dist/agent-loop.js +2 -1
- package/dist/backlog.js +166 -0
- package/dist/commands.js +22 -0
- package/dist/compact.js +2 -2
- package/dist/i18n.js +32 -0
- package/dist/index.js +160 -16
- package/dist/system-prompt.js +24 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.61.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `/backlog <text>` — record an improvement idea in `BACKLOG.md` without
|
|
15
|
+
implementing it. Deterministic: a fresh `N<n>` id under the matching
|
|
16
|
+
`P0..P3` section; an optional leading `P0..P3` picks the section, the first
|
|
17
|
+
line is the title, the rest the body. `/backlog collapse` prunes the
|
|
18
|
+
archived blocks.
|
|
19
|
+
- BACKLOG.md maintenance (`src/backlog.ts`, pure/tested): `/improve` now
|
|
20
|
+
auto-prunes a finished item's archived `<details>` copy after a successful
|
|
21
|
+
run (the `### X. [x] ... done` summary line is kept), and a startup warning
|
|
22
|
+
fires when BACKLOG.md exceeds 500 lines / 60 KB. The collapser tolerates an
|
|
23
|
+
UNCLOSED `<details>` (a real file had one).
|
|
24
|
+
- Dev-mode self-improvement note: in `--dev` the system prompt tells the model
|
|
25
|
+
it may append ONE short BACKLOG.md bullet when it spots an improvement
|
|
26
|
+
outside the current task (off in a normal run).
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Self-development commands (`/improve`, `/backlog`, `/self-review`,
|
|
31
|
+
`/self-fix`, `/self-done`, `/self-list`, `/self-diff`, `/self-apply`) are now
|
|
32
|
+
DEV-ONLY: they are hidden from `/help` and the «/» hints, and rejected by the
|
|
33
|
+
main loop, unless the operator runs in dev mode (`--dev` or
|
|
34
|
+
`config.hotReload`). A regular package install no longer advertises them, and
|
|
35
|
+
a hand-typed `/improve` can no longer edit an unrelated project's
|
|
36
|
+
BACKLOG.md. The list is `DEV_ONLY_COMMANDS` / `isDevOnlyCommand()` in
|
|
37
|
+
`src/commands.ts` (pure, tested).
|
|
38
|
+
|
|
10
39
|
## [2.60.0]
|
|
11
40
|
|
|
12
41
|
### Added
|
|
@@ -220,7 +249,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
220
249
|
- Session banner warns when no saved DeepSeek session exists.
|
|
221
250
|
- `--no-color` flag (explicit `NO_COLOR`).
|
|
222
251
|
|
|
223
|
-
[Unreleased]: https://github.com/Viqto0r/zames_pro/compare/v2.
|
|
252
|
+
[Unreleased]: https://github.com/Viqto0r/zames_pro/compare/v2.61.0...HEAD
|
|
253
|
+
[2.61.0]: https://github.com/Viqto0r/zames_pro/compare/v2.60.0...v2.61.0
|
|
224
254
|
[2.60.0]: https://github.com/Viqto0r/zames_pro/compare/v2.59.0...v2.60.0
|
|
225
255
|
[2.59.0]: https://github.com/Viqto0r/zames_pro/compare/v2.58.0...v2.59.0
|
|
226
256
|
[2.58.0]: https://github.com/Viqto0r/zames_pro/compare/v2.57.1...v2.58.0
|
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
[](https://github.com/Viqto0r/zames_pro/actions/workflows/test.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](package.json)
|
|
8
|
-
[](CONTRIBUTING.md)
|
|
9
8
|
|
|
10
9
|

|
|
11
10
|
|
|
@@ -29,8 +28,6 @@ directory, reads and edits files, runs commands, and commits to git.
|
|
|
29
28
|
custom commands from the repo and `~/.zames`, the same idea as Codex / Claude
|
|
30
29
|
Code.
|
|
31
30
|
- **MCP support** — plug in external tool servers (e.g. `@playwright/mcp`).
|
|
32
|
-
- **Self-review** — `/self-review` snapshots `src/` so the agent can review and
|
|
33
|
-
fix itself in a sandbox (`/self-fix`, `/self-apply`).
|
|
34
31
|
- **Scheduling** — `/loop`, `/cron` and `/jobs` repeat tasks on a timer.
|
|
35
32
|
- **Bilingual UI** — Russian / English (`/config lang`).
|
|
36
33
|
|
|
@@ -47,7 +44,7 @@ directory, reads and edits files, runs commands, and commits to git.
|
|
|
47
44
|
|
|
48
45
|
## Requirements
|
|
49
46
|
|
|
50
|
-
- Node.js >= 20
|
|
47
|
+
- Node.js >= 20
|
|
51
48
|
- A DeepSeek account. On first launch zames asks for your DeepSeek
|
|
52
49
|
login/password in the terminal (and stores them in `~/.zames/config.json`
|
|
53
50
|
after a successful sign-in, so a later logout is handled automatically
|
|
@@ -89,7 +86,6 @@ Credentials and toggles can also be edited from `/config`
|
|
|
89
86
|
|
|
90
87
|
- npm: <https://www.npmjs.com/package/zames_pro>
|
|
91
88
|
- Changelog: [`CHANGELOG.md`](CHANGELOG.md)
|
|
92
|
-
- Contributing: [`CONTRIBUTING.md`](CONTRIBUTING.md)
|
|
93
89
|
- Security policy: [`SECURITY.md`](SECURITY.md)
|
|
94
90
|
|
|
95
91
|
## Installation
|
|
@@ -247,10 +243,6 @@ Codex CLI:
|
|
|
247
243
|
instead of the whole list.
|
|
248
244
|
- /review [focus] [--staged] — ask the agent to review uncommitted changes
|
|
249
245
|
and report findings (no code changes).
|
|
250
|
-
- /improve [id] — self-improvement loop: take the next open item from
|
|
251
|
-
`BACKLOG.md` (or a specific id, e.g. `/improve B3`), implement it, run the
|
|
252
|
-
typecheck/lint/tests, mark it done and add a CHANGELOG entry. Nothing is
|
|
253
|
-
committed — the changes stay in the working tree for review.
|
|
254
246
|
- /plan [on|off] — plan (read-only) mode. While it is on, the mutating tools
|
|
255
247
|
(Write/Edit/MultiEdit/ApplyPatch/Bash, GitAdd/GitCommit/GitPush) are removed
|
|
256
248
|
from the tool set, so the agent can investigate without touching the tree.
|
|
@@ -384,7 +376,7 @@ Changes are written to the project `.zamesrc.json` and applied right away
|
|
|
384
376
|
(help, messages, spinner) and the language the agent answers you in. The
|
|
385
377
|
locale lives in `ui.locale` in the config file.
|
|
386
378
|
|
|
387
|
-
Agent data is stored in `~/.zames`: browser profile, logs, undo history,
|
|
379
|
+
Agent data is stored in `~/.zames`: browser profile, logs, undo history, sessions.
|
|
388
380
|
|
|
389
381
|
## FAQ
|
|
390
382
|
|
package/dist/agent-loop.js
CHANGED
|
@@ -6,7 +6,7 @@ import { translate } from './i18n.js';
|
|
|
6
6
|
import { substituteAttachmentMarkers } from './commands.js';
|
|
7
7
|
import { normText } from './browser.js';
|
|
8
8
|
import { loadHooks, runPreToolUse, runPostToolUse, } from './hooks.js';
|
|
9
|
-
export async function runAgentLoop({ browser, tools, task, workdir, maxIterations = 0, freshChat = false, sendSystemPrompt = false, transcript = null, attachments = [], onThinking = () => { }, onSendPause = () => { }, onSendState = () => { }, onNotice = () => { }, onAssistantThought = () => { }, onToolCall = () => { }, onToolResult = () => { }, onAssistantMessage = () => { }, onChatReady = () => { }, onWarning = () => { }, debugLog = false, locale = 'ru', askDeadlineMs = 240_000, maxAfterToolRetries = 6, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, hooks = undefined, }) {
|
|
9
|
+
export async function runAgentLoop({ browser, tools, task, workdir, maxIterations = 0, freshChat = false, sendSystemPrompt = false, transcript = null, attachments = [], onThinking = () => { }, onSendPause = () => { }, onSendState = () => { }, onNotice = () => { }, onAssistantThought = () => { }, onToolCall = () => { }, onToolResult = () => { }, onAssistantMessage = () => { }, onChatReady = () => { }, onWarning = () => { }, debugLog = false, locale = 'ru', askDeadlineMs = 240_000, maxAfterToolRetries = 6, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, hooks = undefined, selfImprovement = false, }) {
|
|
10
10
|
// Resolve the hook config ONCE per task: a read per tool call would be
|
|
11
11
|
// wasteful, and a mid-task edit of hooks.json is not something to chase.
|
|
12
12
|
// `undefined` means "read .zames/hooks.json"; an explicit null disables
|
|
@@ -102,6 +102,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
102
102
|
gitContext: gitText,
|
|
103
103
|
locale,
|
|
104
104
|
context,
|
|
105
|
+
selfImprovement,
|
|
105
106
|
});
|
|
106
107
|
transcript?.log('system_prompt', {
|
|
107
108
|
length: systemPrompt.length,
|
package/dist/backlog.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { parseBacklogItems } from './commands.js';
|
|
2
|
+
// BACKLOG.md maintenance — the pure half of /backlog and the automatic pruning
|
|
3
|
+
// that runs after /improve. BACKLOG.md is the agent's own improvement-notes
|
|
4
|
+
// file (gitignored); it is READ by /improve, so keeping it small matters —
|
|
5
|
+
// every finished item used to keep a full copy of its original text inside a
|
|
6
|
+
// <details> block, and the file grew without bound.
|
|
7
|
+
//
|
|
8
|
+
// Everything here is pure and unit-tested; index.ts only reads/writes the file.
|
|
9
|
+
const NL = String.fromCharCode(10);
|
|
10
|
+
// Agent-appended items use this id prefix. A distinct letter keeps them apart
|
|
11
|
+
// from the hand-numbered A/B/C/D/E entries, so it is obvious which notes the
|
|
12
|
+
// agent added on its own.
|
|
13
|
+
const NEW_ITEM_PREFIX = 'N';
|
|
14
|
+
// Past these thresholds BACKLOG.md costs more tokens (on /improve) than it is
|
|
15
|
+
// worth, so the operator is nudged to collapse it.
|
|
16
|
+
export const BACKLOG_WARN_LINES = 500;
|
|
17
|
+
export const BACKLOG_WARN_CHARS = 60_000;
|
|
18
|
+
function squashBlankLines(lines) {
|
|
19
|
+
const out = [];
|
|
20
|
+
let blanks = 0;
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
if (line.trim() === '') {
|
|
23
|
+
blanks++;
|
|
24
|
+
if (blanks > 2)
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
blanks = 0;
|
|
29
|
+
}
|
|
30
|
+
out.push(line);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
// A real, kept heading: a `## P<n>` section or a `### X<n>.` item heading that
|
|
35
|
+
// is NOT the archived copy (archived copies carry `~~` right after the id).
|
|
36
|
+
function isRealBoundary(line) {
|
|
37
|
+
const t = line.trim();
|
|
38
|
+
if (/^##\s/.test(t))
|
|
39
|
+
return true;
|
|
40
|
+
return /^###\s+[A-Z]\d+[.]/.test(t) && !/^###\s+[A-Z]\d+[.]\s+~~/.test(t);
|
|
41
|
+
}
|
|
42
|
+
function isDetailsOpen(line) {
|
|
43
|
+
return line.trim().startsWith('<details');
|
|
44
|
+
}
|
|
45
|
+
function isArchivedHeading(line) {
|
|
46
|
+
return /^###\s+[A-Z]\d+[.]\s+~~/.test(line.trim());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Remove archived copies of finished items. A finished item keeps its
|
|
50
|
+
* `### X. [x] ... done` summary line; the `### X. ~~original~~` copy and its
|
|
51
|
+
* body (normally wrapped in a <details> block) are dropped.
|
|
52
|
+
*
|
|
53
|
+
* Deliberately tolerant of an UNCLOSED <details>: a real backlog item was
|
|
54
|
+
* written as `### A1. [x] ...` then `<details>...` with no `</details>`, so the
|
|
55
|
+
* whole file became one nested block. Keying the end of a block on the next
|
|
56
|
+
* REAL heading (a `### X.` without `~~`, or a `## ` section) — not only on
|
|
57
|
+
* `</details>` — keeps the summary lines and the open items even then. Pure and
|
|
58
|
+
* unit-tested; a heading that looks like an item INSIDE an archive is skipped
|
|
59
|
+
* because it carries `~~`.
|
|
60
|
+
*/
|
|
61
|
+
export function collapseBacklog(text) {
|
|
62
|
+
const src = String(text ?? '');
|
|
63
|
+
const lines = src.split(NL);
|
|
64
|
+
const out = [];
|
|
65
|
+
let collapsed = 0;
|
|
66
|
+
let i = 0;
|
|
67
|
+
while (i < lines.length) {
|
|
68
|
+
if (isDetailsOpen(lines[i]) || isArchivedHeading(lines[i])) {
|
|
69
|
+
// Skip to the end of the archive: the first `</details>` OR the first
|
|
70
|
+
// real heading, whichever comes first (see the note above).
|
|
71
|
+
let j = i + 1;
|
|
72
|
+
while (j < lines.length) {
|
|
73
|
+
const t = lines[j].trim();
|
|
74
|
+
if (t.startsWith('</details>')) {
|
|
75
|
+
j++;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
if (isRealBoundary(t))
|
|
79
|
+
break;
|
|
80
|
+
j++;
|
|
81
|
+
}
|
|
82
|
+
collapsed++;
|
|
83
|
+
i = j;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
out.push(lines[i]);
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
const next = squashBlankLines(out).join(NL);
|
|
90
|
+
return { text: next, changed: next !== src, collapsed };
|
|
91
|
+
}
|
|
92
|
+
export function backlogStats(text) {
|
|
93
|
+
const src = String(text ?? '');
|
|
94
|
+
const items = parseBacklogItems(src);
|
|
95
|
+
let archived = 0;
|
|
96
|
+
for (const line of src.split(NL)) {
|
|
97
|
+
if (line.trim().startsWith('<details'))
|
|
98
|
+
archived++;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
lines: src.split(NL).length,
|
|
102
|
+
chars: src.length,
|
|
103
|
+
open: items.filter((i) => i.status === 'open').length,
|
|
104
|
+
done: items.filter((i) => i.status === 'done').length,
|
|
105
|
+
archived,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** True when the file is big enough to deserve a collapse nudge. */
|
|
109
|
+
export function backlogNeedsPruning(stats) {
|
|
110
|
+
return stats.lines > BACKLOG_WARN_LINES || stats.chars > BACKLOG_WARN_CHARS;
|
|
111
|
+
}
|
|
112
|
+
/** The next free N<n> id not yet used in the file. */
|
|
113
|
+
export function nextBacklogId(text) {
|
|
114
|
+
let max = 0;
|
|
115
|
+
const re = /^###[ ]+([A-Z])([0-9]+)[.]/gm;
|
|
116
|
+
let m;
|
|
117
|
+
while ((m = re.exec(String(text ?? '')))) {
|
|
118
|
+
if (m[1] === NEW_ITEM_PREFIX)
|
|
119
|
+
max = Math.max(max, Number(m[2]));
|
|
120
|
+
}
|
|
121
|
+
return NEW_ITEM_PREFIX + (max + 1);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Insert a new OPEN item under the matching `## P<n>` section (or append a new
|
|
125
|
+
* section at the end). Returns the new file text and the generated id. Pure;
|
|
126
|
+
* the caller writes the file.
|
|
127
|
+
*/
|
|
128
|
+
export function appendBacklogItem(text, item) {
|
|
129
|
+
const src = String(text ?? '');
|
|
130
|
+
const title = String(item.title ?? '')
|
|
131
|
+
.trim()
|
|
132
|
+
.replace(/ {2,}/g, ' ');
|
|
133
|
+
const prio = /^P[0-3]$/i.test(String(item.priority ?? '').trim())
|
|
134
|
+
? String(item.priority).trim().toUpperCase()
|
|
135
|
+
: 'P2';
|
|
136
|
+
const note = String(item.note ?? '').trim();
|
|
137
|
+
const id = nextBacklogId(src);
|
|
138
|
+
const entry = ['### ' + id + '. ' + title, ''];
|
|
139
|
+
if (note) {
|
|
140
|
+
for (const l of note.split(NL))
|
|
141
|
+
entry.push(l);
|
|
142
|
+
entry.push('');
|
|
143
|
+
}
|
|
144
|
+
const lines = src.split(NL);
|
|
145
|
+
const headerRe = new RegExp('^##[ ]+' + prio + '(?![0-9])');
|
|
146
|
+
let at = -1;
|
|
147
|
+
for (let i = 0; i < lines.length; i++) {
|
|
148
|
+
if (headerRe.test(lines[i])) {
|
|
149
|
+
// Insert right after the header and any blank lines that follow it, so
|
|
150
|
+
// the newest note sits at the top of its priority block.
|
|
151
|
+
let j = i + 1;
|
|
152
|
+
while (j < lines.length && lines[j].trim() === '')
|
|
153
|
+
j++;
|
|
154
|
+
at = j;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (at === -1) {
|
|
159
|
+
while (lines.length && lines[lines.length - 1].trim() === '')
|
|
160
|
+
lines.pop();
|
|
161
|
+
const out = lines.concat(['', '## ' + prio, '', ...entry]).join(NL);
|
|
162
|
+
return { text: out, id };
|
|
163
|
+
}
|
|
164
|
+
const out = lines.slice(0, at).concat(entry, lines.slice(at)).join(NL);
|
|
165
|
+
return { text: out, id };
|
|
166
|
+
}
|
package/dist/commands.js
CHANGED
|
@@ -781,6 +781,28 @@ export function buildImprovePrompt(item) {
|
|
|
781
781
|
'(files changed, tests run, whether BACKLOG/CHANGELOG were updated).');
|
|
782
782
|
}
|
|
783
783
|
// ---------- /review ----------
|
|
784
|
+
// Commands that only make sense while developing zames itself. A regular user
|
|
785
|
+
// who installed the package must not see them in /help or the «/» hints, and
|
|
786
|
+
// the main loop rejects them unless dev mode is on (--dev / config.hotReload).
|
|
787
|
+
// Kept here (pure) so the list has ONE source of truth and is unit-tested.
|
|
788
|
+
export const DEV_ONLY_COMMANDS = [
|
|
789
|
+
'/improve',
|
|
790
|
+
'/backlog',
|
|
791
|
+
'/self-review',
|
|
792
|
+
'/self-fix',
|
|
793
|
+
'/self-done',
|
|
794
|
+
'/self-list',
|
|
795
|
+
'/self-diff',
|
|
796
|
+
'/self-apply',
|
|
797
|
+
];
|
|
798
|
+
/** True when the typed line invokes a dev-only command. */
|
|
799
|
+
export function isDevOnlyCommand(text) {
|
|
800
|
+
const name = String(text ?? '')
|
|
801
|
+
.trim()
|
|
802
|
+
.split(/\s+/)[0]
|
|
803
|
+
.toLowerCase();
|
|
804
|
+
return DEV_ONLY_COMMANDS.find((c) => c === name) || null;
|
|
805
|
+
}
|
|
784
806
|
export function buildReviewPrompt(focus, hasStaged = false) {
|
|
785
807
|
const scope = hasStaged ? 'staged' : 'uncommitted';
|
|
786
808
|
const f = String(focus ?? '').trim();
|
package/dist/compact.js
CHANGED
|
@@ -14,7 +14,7 @@ export const COMPACT_FALLBACK_LIMIT = 40;
|
|
|
14
14
|
* The caller owns the browser and the throttle; this function only sends.
|
|
15
15
|
*/
|
|
16
16
|
export async function performCompact(opts) {
|
|
17
|
-
const { browser, workdir, locale, task, transcript = null, ui = null, buildSystemPrompt, tools, answerTimeoutMs, fallbackLimit = COMPACT_FALLBACK_LIMIT, skipUiLock = false, quiet = false, } = opts;
|
|
17
|
+
const { browser, workdir, locale, task, transcript = null, ui = null, buildSystemPrompt, tools, answerTimeoutMs, fallbackLimit = COMPACT_FALLBACK_LIMIT, skipUiLock = false, quiet = false, selfImprovement = false, } = opts;
|
|
18
18
|
const t = translate(locale);
|
|
19
19
|
const log = (msg) => {
|
|
20
20
|
if (!quiet)
|
|
@@ -124,7 +124,7 @@ export async function performCompact(opts) {
|
|
|
124
124
|
});
|
|
125
125
|
// 2) New chat + system prompt + the summary as the first message.
|
|
126
126
|
await browser.newChat();
|
|
127
|
-
await browser.ask(buildSystemPrompt({ workdir, tools, locale }), {
|
|
127
|
+
await browser.ask(buildSystemPrompt({ workdir, tools, locale, selfImprovement }), {
|
|
128
128
|
timeout: 60_000,
|
|
129
129
|
agent: false,
|
|
130
130
|
});
|
package/dist/i18n.js
CHANGED
|
@@ -259,6 +259,34 @@ export const CATALOG = {
|
|
|
259
259
|
ru: 'Улучшение {id}: {title}',
|
|
260
260
|
en: 'Improving {id}: {title}',
|
|
261
261
|
},
|
|
262
|
+
'improve.collapsed': {
|
|
263
|
+
ru: 'BACKLOG свёрнут: удалено архивных блоков — {n}.',
|
|
264
|
+
en: 'BACKLOG collapsed: {n} archived block(s) removed.',
|
|
265
|
+
},
|
|
266
|
+
'help.cmd.backlog': {
|
|
267
|
+
ru: '/backlog <текст> записать идею в BACKLOG.md',
|
|
268
|
+
en: '/backlog <text> record an idea in BACKLOG.md',
|
|
269
|
+
},
|
|
270
|
+
'backlog.usage': {
|
|
271
|
+
ru: 'Использование: /backlog <текст идеи>',
|
|
272
|
+
en: 'Usage: /backlog <idea text>',
|
|
273
|
+
},
|
|
274
|
+
'backlog.none': {
|
|
275
|
+
ru: 'BACKLOG.md не найден в рабочей директории.',
|
|
276
|
+
en: 'BACKLOG.md was not found in the working directory.',
|
|
277
|
+
},
|
|
278
|
+
'backlog.adding': {
|
|
279
|
+
ru: 'Записываю в BACKLOG: {v}',
|
|
280
|
+
en: 'Recording in BACKLOG: {v}',
|
|
281
|
+
},
|
|
282
|
+
'backlog.warn': {
|
|
283
|
+
ru: 'BACKLOG.md разросся ({lines} строк, {chars} символов). Сверни сделанное: /backlog collapse',
|
|
284
|
+
en: 'BACKLOG.md has grown large ({lines} lines, {chars} chars). Collapse it: /backlog collapse',
|
|
285
|
+
},
|
|
286
|
+
'backlog.empty': {
|
|
287
|
+
ru: 'Свёртывать нечего: архивных блоков нет.',
|
|
288
|
+
en: 'Nothing to collapse: there are no archived blocks.',
|
|
289
|
+
},
|
|
262
290
|
'help.cmd.compact': {
|
|
263
291
|
ru: '/compact сжать историю и открыть новый чат с резюме',
|
|
264
292
|
en: '/compact compact the history and open a new chat with the summary',
|
|
@@ -680,6 +708,10 @@ export const CATALOG = {
|
|
|
680
708
|
ru: 'Неизвестная команда: {v}. Набери /help.',
|
|
681
709
|
en: 'Unknown command: {v}. Type /help.',
|
|
682
710
|
},
|
|
711
|
+
'msg.dev_only': {
|
|
712
|
+
ru: 'Команда {v} доступна только в dev-режиме (--dev или hotReload).',
|
|
713
|
+
en: 'The {v} command is only available in dev mode (--dev or hotReload).',
|
|
714
|
+
},
|
|
683
715
|
'msg.bye': { ru: 'Выход.', en: 'Bye.' },
|
|
684
716
|
'shell.empty': {
|
|
685
717
|
ru: 'После ! нужна команда, например !git status.',
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n
|
|
|
18
18
|
import { Transcript } from './transcript.js';
|
|
19
19
|
import { UndoStore } from './undo.js';
|
|
20
20
|
import { selfReview, selfDiff, selfApply, selfList } from './self-review.js';
|
|
21
|
-
import { formatDiff, formatDiffStat, formatContextSources, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, resolveExtraDir, buildReviewPrompt, parseBacklogItems, nextBacklogItem, buildImprovePrompt, formatDuration, formatRelativeTime, trimRestoredMessages, RESTORED_HISTORY_LIMIT, mergeMessages, hasQueuedJob, isSlashCommand, parseQueueCommand, parseLiveToggle, parseGoalCommand, isLiveConfigCommand, withGoal, formatQueueList, ctrlCEscalation, } from './commands.js';
|
|
21
|
+
import { formatDiff, formatDiffStat, formatContextSources, diffGitArgs, parseTranscript, summarizeTranscript, renderCost, formatExport, defaultExportPath, renderDoctor, resolveExtraDir, buildReviewPrompt, isDevOnlyCommand, parseBacklogItems, nextBacklogItem, buildImprovePrompt, formatDuration, formatRelativeTime, trimRestoredMessages, RESTORED_HISTORY_LIMIT, mergeMessages, hasQueuedJob, isSlashCommand, parseQueueCommand, parseLiveToggle, parseGoalCommand, isLiveConfigCommand, withGoal, formatQueueList, ctrlCEscalation, } from './commands.js';
|
|
22
22
|
import { performCompact } from './compact.js';
|
|
23
23
|
import { Scheduler, parseInterval, formatInterval, formatJobLine, parseCron, } from './scheduler.js';
|
|
24
24
|
import { renderMarkdown, setAnswerWidth } from './markdown.js';
|
|
@@ -192,6 +192,7 @@ const RELOADABLE = [
|
|
|
192
192
|
'spinner',
|
|
193
193
|
'config',
|
|
194
194
|
'fsutil',
|
|
195
|
+
'backlog',
|
|
195
196
|
];
|
|
196
197
|
const mod = {
|
|
197
198
|
createTools,
|
|
@@ -204,6 +205,10 @@ const mod = {
|
|
|
204
205
|
closeWeb,
|
|
205
206
|
createSpinner,
|
|
206
207
|
createMcpPool: null,
|
|
208
|
+
collapseBacklog: null,
|
|
209
|
+
appendBacklogItem: null,
|
|
210
|
+
backlogStats: null,
|
|
211
|
+
backlogNeedsPruning: null,
|
|
207
212
|
};
|
|
208
213
|
async function reloadModules() {
|
|
209
214
|
const stamp = Date.now();
|
|
@@ -242,6 +247,14 @@ async function reloadModules() {
|
|
|
242
247
|
mod.createSpinner = pick('spinner', 'createSpinner');
|
|
243
248
|
if (pick('mcp', 'createMcpPool'))
|
|
244
249
|
mod.createMcpPool = pick('mcp', 'createMcpPool');
|
|
250
|
+
if (pick('backlog', 'collapseBacklog'))
|
|
251
|
+
mod.collapseBacklog = pick('backlog', 'collapseBacklog');
|
|
252
|
+
if (pick('backlog', 'appendBacklogItem'))
|
|
253
|
+
mod.appendBacklogItem = pick('backlog', 'appendBacklogItem');
|
|
254
|
+
if (pick('backlog', 'backlogStats'))
|
|
255
|
+
mod.backlogStats = pick('backlog', 'backlogStats');
|
|
256
|
+
if (pick('backlog', 'backlogNeedsPruning'))
|
|
257
|
+
mod.backlogNeedsPruning = pick('backlog', 'backlogNeedsPruning');
|
|
245
258
|
return { count: loaded.size, errors };
|
|
246
259
|
}
|
|
247
260
|
// Initial load so that mod.buildSystemPrompt and the rest are populated.
|
|
@@ -297,6 +310,42 @@ function warnIfBrowserChanged() {
|
|
|
297
310
|
}
|
|
298
311
|
// ---------- helpers ----------
|
|
299
312
|
function printHelp() {
|
|
313
|
+
// Self-development commands (backlog / self-review) are shown ONLY in dev
|
|
314
|
+
// mode. A regular package install must not advertise them to the user — and
|
|
315
|
+
// the main loop rejects them too (see the guard at the dispatch).
|
|
316
|
+
const devHelp = devMode
|
|
317
|
+
? ' ' +
|
|
318
|
+
t('help.cmd.improve') +
|
|
319
|
+
'\n ' +
|
|
320
|
+
t('help.cmd.backlog') +
|
|
321
|
+
'\n ' +
|
|
322
|
+
t('help.cmd.debug_dom') +
|
|
323
|
+
'\n ' +
|
|
324
|
+
t('help.cmd.help') +
|
|
325
|
+
'\n ' +
|
|
326
|
+
t('help.cmd.exit') +
|
|
327
|
+
'\n\n' +
|
|
328
|
+
theme.bold(t('help.self_review')) +
|
|
329
|
+
'\n ' +
|
|
330
|
+
t('help.self.review') +
|
|
331
|
+
'\n ' +
|
|
332
|
+
t('help.self.fix') +
|
|
333
|
+
'\n ' +
|
|
334
|
+
t('help.self.done') +
|
|
335
|
+
'\n ' +
|
|
336
|
+
t('help.self.list') +
|
|
337
|
+
'\n ' +
|
|
338
|
+
t('help.self.diff') +
|
|
339
|
+
'\n ' +
|
|
340
|
+
t('help.self.apply') +
|
|
341
|
+
'\n\n'
|
|
342
|
+
: ' ' +
|
|
343
|
+
t('help.cmd.debug_dom') +
|
|
344
|
+
'\n ' +
|
|
345
|
+
t('help.cmd.help') +
|
|
346
|
+
'\n ' +
|
|
347
|
+
t('help.cmd.exit') +
|
|
348
|
+
'\n';
|
|
300
349
|
console.log(`
|
|
301
350
|
${theme.bold('zames')} — ${t('app.tagline')}
|
|
302
351
|
|
|
@@ -388,19 +437,7 @@ ${theme.bold(t('help.sec.files'))}
|
|
|
388
437
|
${t('help.cmd.doctor')}
|
|
389
438
|
${t('help.cmd.add_dir')}
|
|
390
439
|
${t('help.cmd.review')}
|
|
391
|
-
|
|
392
|
-
${t('help.cmd.debug_dom')}
|
|
393
|
-
${t('help.cmd.help')}
|
|
394
|
-
${t('help.cmd.exit')}
|
|
395
|
-
|
|
396
|
-
${theme.bold(t('help.self_review'))}
|
|
397
|
-
${t('help.self.review')}
|
|
398
|
-
${t('help.self.fix')}
|
|
399
|
-
${t('help.self.done')}
|
|
400
|
-
${t('help.self.list')}
|
|
401
|
-
${t('help.self.diff')}
|
|
402
|
-
${t('help.self.apply')}
|
|
403
|
-
${dynamicCommands.length ? '\n' + theme.bold(t('help.skills')) + '\n' + dynamicCommands.map((d) => ' ' + d.name.padEnd(24) + ' ' + d.description).join('\n') : ''}
|
|
440
|
+
${devHelp}${dynamicCommands.length ? '\n' + theme.bold(t('help.skills')) + '\n' + dynamicCommands.map((d) => ' ' + d.name.padEnd(24) + ' ' + d.description).join('\n') : ''}
|
|
404
441
|
|
|
405
442
|
${theme.bold(t('help.files'))}
|
|
406
443
|
${t('help.files.logs')} ${config.transcript.dir}
|
|
@@ -421,6 +458,12 @@ function dirLabel(p) {
|
|
|
421
458
|
// List of slash commands for completion when you type «/» (Tab — complete).
|
|
422
459
|
// The description is localized by the help.cmd.* key at display time — see
|
|
423
460
|
// buildSlashCommands().
|
|
461
|
+
//
|
|
462
|
+
// Dev-only commands (backlog-driven self-improvement, self-review snapshots)
|
|
463
|
+
// are filtered out of the hints and rejected by the main loop unless the
|
|
464
|
+
// operator is in dev mode — a regular package install must never advertise
|
|
465
|
+
// them, and a muscle-memory `/improve` must not edit the user's BACKLOG.md.
|
|
466
|
+
// The list itself lives in commands.ts (`isDevOnlyCommand`), one source.
|
|
424
467
|
const SLASH_COMMANDS = [
|
|
425
468
|
{ name: '/help', key: 'help.cmd.help' },
|
|
426
469
|
{ name: '/new', key: 'help.cmd.new' },
|
|
@@ -450,6 +493,7 @@ const SLASH_COMMANDS = [
|
|
|
450
493
|
{ name: '/add-dir', key: 'help.cmd.add_dir' },
|
|
451
494
|
{ name: '/review', key: 'help.cmd.review' },
|
|
452
495
|
{ name: '/improve', key: 'help.cmd.improve' },
|
|
496
|
+
{ name: '/backlog', key: 'help.cmd.backlog' },
|
|
453
497
|
{ name: '/goal', key: 'help.cmd.goal' },
|
|
454
498
|
{ name: '/loop', key: 'help.cmd.loop' },
|
|
455
499
|
{ name: '/cron', key: 'help.cmd.cron' },
|
|
@@ -548,8 +592,10 @@ async function expandSlashTarget(workdir, name, rest) {
|
|
|
548
592
|
return null;
|
|
549
593
|
}
|
|
550
594
|
// Slash-command descriptions in the current language (for LineEditor hints).
|
|
595
|
+
// Dev-only commands are filtered out unless the operator is in dev mode, so a
|
|
596
|
+
// regular install never advertises (or accepts) them.
|
|
551
597
|
function buildSlashCommands() {
|
|
552
|
-
const base = SLASH_COMMANDS.map((c) => ({
|
|
598
|
+
const base = SLASH_COMMANDS.filter((c) => devMode || !isDevOnlyCommand(c.name)).map((c) => ({
|
|
553
599
|
name: c.name,
|
|
554
600
|
description: t(c.key),
|
|
555
601
|
}));
|
|
@@ -1056,7 +1102,7 @@ async function printRestoredHistory(browser, ui, chatId = null) {
|
|
|
1056
1102
|
}
|
|
1057
1103
|
// ---------- task runner ----------
|
|
1058
1104
|
async function runTask(browser, tools, taskText, workdir, opts, attachments = []) {
|
|
1059
|
-
const { transcript, freshChat, sendSystemPrompt, queue = [], ui: editor, onChatReady, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, goal = null, todosQuery = null, } = opts;
|
|
1105
|
+
const { transcript, freshChat, sendSystemPrompt, queue = [], ui: editor, onChatReady, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, goal = null, todosQuery = null, selfImprovement = false, } = opts;
|
|
1060
1106
|
// In TTY mode the UI is a LineEditor: it owns the input (queue, Esc,
|
|
1061
1107
|
// Ctrl+C) and draws the status ABOVE the permanent input line. In non-TTY
|
|
1062
1108
|
// mode (pipes) — a regular spinner + watchInput.
|
|
@@ -1166,6 +1212,7 @@ async function runTask(browser, tools, taskText, workdir, opts, attachments = []
|
|
|
1166
1212
|
autoCompactPct,
|
|
1167
1213
|
contextLimit,
|
|
1168
1214
|
getTokenUsage,
|
|
1215
|
+
selfImprovement,
|
|
1169
1216
|
});
|
|
1170
1217
|
// The loop may end WITHOUT a model answer: an exhausted iteration
|
|
1171
1218
|
// limit or an ask() watchdog (the model stopped responding). In that
|
|
@@ -1402,6 +1449,7 @@ async function main() {
|
|
|
1402
1449
|
transcript,
|
|
1403
1450
|
freshChat,
|
|
1404
1451
|
sendSystemPrompt,
|
|
1452
|
+
selfImprovement: devMode,
|
|
1405
1453
|
onChatReady: (chatId) => {
|
|
1406
1454
|
if (chatId)
|
|
1407
1455
|
saveLastChat(chatId, currentWorkdir);
|
|
@@ -1423,6 +1471,21 @@ async function main() {
|
|
|
1423
1471
|
if (!authMarkerExists()) {
|
|
1424
1472
|
console.log(theme.warn(t('msg.first_login_hint')));
|
|
1425
1473
|
}
|
|
1474
|
+
// Nudge when BACKLOG.md has grown large: /improve reads the whole file, so a
|
|
1475
|
+
// bloated backlog costs tokens on every self-improvement run. Best-effort.
|
|
1476
|
+
if (devMode) {
|
|
1477
|
+
try {
|
|
1478
|
+
const bl = await fs.readFile(path.join(currentWorkdir, 'BACKLOG.md'), 'utf-8');
|
|
1479
|
+
const st = mod.backlogStats(bl);
|
|
1480
|
+
if (mod.backlogNeedsPruning(st)) {
|
|
1481
|
+
console.log(theme.warn(t('backlog.warn', {
|
|
1482
|
+
lines: String(st.lines),
|
|
1483
|
+
chars: String(st.chars),
|
|
1484
|
+
})));
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
catch { }
|
|
1488
|
+
}
|
|
1426
1489
|
let freshChatNext = true;
|
|
1427
1490
|
let sendSystemPromptNext = true;
|
|
1428
1491
|
// Resuming an existing chat already has the system-prompt at its start, so
|
|
@@ -2226,6 +2289,16 @@ async function main() {
|
|
|
2226
2289
|
const lower = trimmed.toLowerCase();
|
|
2227
2290
|
if (['/exit', '/quit', 'exit', 'quit'].includes(lower))
|
|
2228
2291
|
break;
|
|
2292
|
+
// Dev-only commands (backlog / self-review) must not run outside dev mode.
|
|
2293
|
+
// Hiding them from /help is not enough: a user could type the command by
|
|
2294
|
+
// hand, and it would rewrite their project's BACKLOG.md or snapshot src/.
|
|
2295
|
+
if (!devMode) {
|
|
2296
|
+
const devCmd = isDevOnlyCommand(lower);
|
|
2297
|
+
if (devCmd) {
|
|
2298
|
+
console.error(theme.warn(t('msg.dev_only', { v: devCmd })));
|
|
2299
|
+
continue;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2229
2302
|
// `!command` — run a shell command directly, without asking the model
|
|
2230
2303
|
// (like Claude Code's bash mode). The same sandbox guard as the Bash tool
|
|
2231
2304
|
// applies, so a direct command cannot leave the project either.
|
|
@@ -3328,6 +3401,7 @@ async function main() {
|
|
|
3328
3401
|
tools: mod.createTools(currentWorkdir, { undo, todos: todoStore }),
|
|
3329
3402
|
answerTimeoutMs: config.browser.answerTimeoutMs,
|
|
3330
3403
|
fallbackLimit: COMPACT_FALLBACK_LIMIT,
|
|
3404
|
+
selfImprovement: devMode,
|
|
3331
3405
|
});
|
|
3332
3406
|
if (res.ok) {
|
|
3333
3407
|
currentChatId = res.chatId;
|
|
@@ -3382,12 +3456,80 @@ async function main() {
|
|
|
3382
3456
|
}
|
|
3383
3457
|
},
|
|
3384
3458
|
getTokenUsage: () => browser.getLastTokenUsage(),
|
|
3459
|
+
selfImprovement: devMode,
|
|
3385
3460
|
});
|
|
3386
3461
|
}
|
|
3387
3462
|
finally {
|
|
3388
3463
|
if (editor)
|
|
3389
3464
|
editor.busy = false;
|
|
3390
3465
|
}
|
|
3466
|
+
// Auto-prune: a finished item keeps its summary line but loses the
|
|
3467
|
+
// archived <details> copy of its original text, so BACKLOG.md does not
|
|
3468
|
+
// grow on every /improve. Best-effort — a read/write failure must not
|
|
3469
|
+
// break the command.
|
|
3470
|
+
try {
|
|
3471
|
+
const raw = await fs.readFile(path.join(currentWorkdir, 'BACKLOG.md'), 'utf-8');
|
|
3472
|
+
const col = mod.collapseBacklog(raw);
|
|
3473
|
+
if (col.changed) {
|
|
3474
|
+
await fs.writeFile(path.join(currentWorkdir, 'BACKLOG.md'), col.text, 'utf-8');
|
|
3475
|
+
console.log(theme.dim(t('improve.collapsed', { n: String(col.collapsed) })));
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
catch { }
|
|
3479
|
+
continue;
|
|
3480
|
+
}
|
|
3481
|
+
if (lower === '/backlog' || lower.startsWith('/backlog ')) {
|
|
3482
|
+
// Record an improvement idea without implementing it. `/backlog
|
|
3483
|
+
// collapse` prunes the archived blocks instead. The LLM only PLACES
|
|
3484
|
+
// the item; the pure helpers in src/backlog.ts own the format.
|
|
3485
|
+
const note = trimmed.slice('/backlog'.length).trim();
|
|
3486
|
+
const backlogFile = path.join(currentWorkdir, 'BACKLOG.md');
|
|
3487
|
+
let backlogText;
|
|
3488
|
+
try {
|
|
3489
|
+
backlogText = await fs.readFile(backlogFile, 'utf-8');
|
|
3490
|
+
}
|
|
3491
|
+
catch {
|
|
3492
|
+
console.error(theme.warn(t('backlog.none')));
|
|
3493
|
+
continue;
|
|
3494
|
+
}
|
|
3495
|
+
if (!note) {
|
|
3496
|
+
console.log(theme.dim(t('backlog.usage')));
|
|
3497
|
+
continue;
|
|
3498
|
+
}
|
|
3499
|
+
if (note === 'collapse') {
|
|
3500
|
+
const col = mod.collapseBacklog(backlogText);
|
|
3501
|
+
if (!col.changed) {
|
|
3502
|
+
console.log(theme.dim(t('backlog.empty')));
|
|
3503
|
+
}
|
|
3504
|
+
else {
|
|
3505
|
+
await fs.writeFile(backlogFile, col.text, 'utf-8');
|
|
3506
|
+
console.log(theme.system(t('improve.collapsed', { n: String(col.collapsed) })));
|
|
3507
|
+
}
|
|
3508
|
+
continue;
|
|
3509
|
+
}
|
|
3510
|
+
// Deterministic append: no model round-trip is needed to record a
|
|
3511
|
+
// one-line note, and a pure helper cannot invent a different id than
|
|
3512
|
+
// the one the operator is shown. An optional leading `P0..P3` sets the
|
|
3513
|
+
// priority; the first line is the title, the rest becomes the body.
|
|
3514
|
+
let priority = 'P2';
|
|
3515
|
+
let body = note;
|
|
3516
|
+
for (const p of ['P0', 'P1', 'P2', 'P3']) {
|
|
3517
|
+
if (note.toUpperCase().startsWith(p + ' ')) {
|
|
3518
|
+
priority = p;
|
|
3519
|
+
body = note.slice(p.length + 1);
|
|
3520
|
+
break;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
const bodyLines = body.split(String.fromCharCode(10));
|
|
3524
|
+
const title = bodyLines[0];
|
|
3525
|
+
const rest = bodyLines.slice(1).join(String.fromCharCode(10)).trim();
|
|
3526
|
+
const res = mod.appendBacklogItem(backlogText, {
|
|
3527
|
+
title,
|
|
3528
|
+
priority,
|
|
3529
|
+
note: rest || undefined,
|
|
3530
|
+
});
|
|
3531
|
+
await fs.writeFile(backlogFile, res.text, 'utf-8');
|
|
3532
|
+
console.log(theme.system(t('backlog.adding', { v: res.id })));
|
|
3391
3533
|
continue;
|
|
3392
3534
|
}
|
|
3393
3535
|
if (lower === '/review' || lower.startsWith('/review ')) {
|
|
@@ -3578,6 +3720,7 @@ async function main() {
|
|
|
3578
3720
|
answerTimeoutMs: config.browser.answerTimeoutMs,
|
|
3579
3721
|
quiet: true,
|
|
3580
3722
|
skipUiLock: true,
|
|
3723
|
+
selfImprovement: devMode,
|
|
3581
3724
|
});
|
|
3582
3725
|
if (res.ok) {
|
|
3583
3726
|
currentChatId = res.chatId;
|
|
@@ -3591,6 +3734,7 @@ async function main() {
|
|
|
3591
3734
|
getTokenUsage: () => browser.getLastTokenUsage(),
|
|
3592
3735
|
goal: sessionGoal,
|
|
3593
3736
|
todosQuery: () => todosSummary(),
|
|
3737
|
+
selfImprovement: devMode,
|
|
3594
3738
|
}, inputAttachments);
|
|
3595
3739
|
}
|
|
3596
3740
|
finally {
|
package/dist/system-prompt.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { translate } from './i18n.js';
|
|
2
|
+
// The dev-mode BACKLOG instruction. English (agent-facing) like the rest of the
|
|
3
|
+
// prompt. Deliberately narrow: one short note at most, and only when the idea
|
|
4
|
+
// is OUTSIDE the current task — otherwise the model documents trivia instead
|
|
5
|
+
// of doing the work, and BACKLOG.md grows on every run.
|
|
6
|
+
const BACKLOG_SECTION = `## Self-improvement notes (dev mode)
|
|
7
|
+
|
|
8
|
+
You are running in the operator's dev mode, so this project is zames itself
|
|
9
|
+
and you may improve it. While working, if you notice a concrete improvement
|
|
10
|
+
that is OUTSIDE the current task — a bug in another module, a cleanup, a
|
|
11
|
+
missing test — append ONE short bullet to BACKLOG.md (Write/Edit) instead of
|
|
12
|
+
silently forgetting it, then continue the task.
|
|
13
|
+
|
|
14
|
+
Rules for that note:
|
|
15
|
+
- at most one or two per task; do NOT let it distract from the task;
|
|
16
|
+
- do NOT duplicate an item already in BACKLOG.md (read it first if unsure);
|
|
17
|
+
- keep it to a single line: what, why it matters, and a one-line fix sketch;
|
|
18
|
+
- put it under the matching priority section (P0 bug/data loss, P1 noticeable
|
|
19
|
+
pain, P2 quality, P3 nice-to-have);
|
|
20
|
+
- this is the ONE exception to "do not touch unrelated files". If nothing
|
|
21
|
+
worth noting, add nothing.
|
|
22
|
+
`;
|
|
2
23
|
/** Render the AGENTS.md / MEMORY / skills / commands blocks of the prompt. */
|
|
3
24
|
export function renderContextSection(context) {
|
|
4
25
|
if (!context)
|
|
@@ -62,7 +83,7 @@ export function renderContextSection(context) {
|
|
|
62
83
|
}
|
|
63
84
|
return out;
|
|
64
85
|
}
|
|
65
|
-
export function buildSystemPrompt({ workdir, tools, gitContext = null, locale = 'ru', attachments = [], context = null, }) {
|
|
86
|
+
export function buildSystemPrompt({ workdir, tools, gitContext = null, locale = 'ru', attachments = [], context = null, selfImprovement = false, }) {
|
|
66
87
|
const t = translate(locale);
|
|
67
88
|
const toolDescriptions = tools
|
|
68
89
|
.map((t2) => `### ${t2.name}\n${t2.description}\nParameters: ${JSON.stringify(t2.parameters)}`)
|
|
@@ -75,6 +96,7 @@ export function buildSystemPrompt({ workdir, tools, gitContext = null, locale =
|
|
|
75
96
|
`Each [image#N] / [file#N] marker corresponds to a file the user pasted into the terminal; the file was uploaded to the chat and also saved under <project>/tmp. Look at the images in the chat; for files, read the copy in tmp if you need the contents.\n`
|
|
76
97
|
: '';
|
|
77
98
|
const contextSection = renderContextSection(context);
|
|
99
|
+
const selfSection = selfImprovement ? '\n' + BACKLOG_SECTION + '\n' : '';
|
|
78
100
|
return `You are a coding agent running in a terminal. You help the user with software engineering tasks by reading files, writing code, running commands, and iterating until the task is done.
|
|
79
101
|
|
|
80
102
|
You work in the directory: ${workdir}
|
|
@@ -127,7 +149,7 @@ task is done (or when you must ask the operator), not between tool calls.
|
|
|
127
149
|
You have access to the following tools:
|
|
128
150
|
|
|
129
151
|
${toolDescriptions}
|
|
130
|
-
${gitSection}${attachSection}${contextSection}
|
|
152
|
+
${gitSection}${attachSection}${contextSection}${selfSection}
|
|
131
153
|
## Choosing the right tool
|
|
132
154
|
|
|
133
155
|
Prefer the most specific tool; a wrong choice wastes a turn:
|
package/package.json
CHANGED