zames_pro 2.77.0 → 2.78.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 CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.78.0] - 2026-10-18
11
+
12
+ ### Added
13
+
14
+ - **skills:** ship the methodology skills inside the package
15
+
16
+ ### Fixed
17
+
18
+ - **fsutil:** remove the temp file when an atomic write fails
19
+
20
+ ## [2.77.1] - 2026-10-17
21
+
22
+ ### Fixed
23
+
24
+ - **backlog:** only collapse real archive blocks, not notes that mention details
25
+ - **qwen:** do not read a retry phrase in the answer as a rate limit
26
+ - **web:** block IPv4-compatible IPv6 addresses in the SSRF guard
27
+ - **sessions:** cap the input history when loading it
28
+ - **sessions:** keep chat ids with unsafe characters in separate files
29
+
10
30
  ## [2.77.0] - 2026-10-17
11
31
 
12
32
  ### Added
package/dist/backlog.js CHANGED
@@ -62,6 +62,20 @@ function isRealBoundary(line) {
62
62
  function isDetailsOpen(line) {
63
63
  return line.trim().startsWith('<details');
64
64
  }
65
+ // An ARCHIVE block is a <details> that actually wraps the struck-through copy
66
+ // of an item heading (`### X. ~~original~~`). A note that merely MENTIONS
67
+ // `Use <details> in README:` must not be treated as an archive: collapsing it
68
+ // deleted the note's own body (everything up to the next heading).
69
+ function isArchiveBlock(lines, from) {
70
+ for (let j = from; j < lines.length; j++) {
71
+ const t = lines[j].trim();
72
+ if (isArchivedHeading(t))
73
+ return true;
74
+ if (t.startsWith('</details>') || isRealBoundary(t))
75
+ return false;
76
+ }
77
+ return false;
78
+ }
65
79
  function isArchivedHeading(line) {
66
80
  return /^###\s+[A-Z]\d+[.]\s+~~/.test(line.trim());
67
81
  }
@@ -85,7 +99,8 @@ export function collapseBacklog(text) {
85
99
  let collapsed = 0;
86
100
  let i = 0;
87
101
  while (i < lines.length) {
88
- if (isDetailsOpen(lines[i]) || isArchivedHeading(lines[i])) {
102
+ const archiveOpen = isDetailsOpen(lines[i]) && isArchiveBlock(lines, i + 1);
103
+ if (archiveOpen || isArchivedHeading(lines[i])) {
89
104
  // Skip to the end of the archive: the first `</details>` OR the first
90
105
  // real heading, whichever comes first (see the note above).
91
106
  let j = i + 1;
package/dist/context.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
2
2
  import { statSync } from 'fs';
3
3
  import path from 'path';
4
4
  import os from 'os';
5
+ import { fileURLToPath } from 'url';
5
6
  // Built-in skills, always available unless a project/global skill with the
6
7
  // same name overrides them (real files win). They give the agent proven,
7
8
  // reusable workflows in the spirit of Claude Code / Codex. English only (the
@@ -362,6 +363,20 @@ export async function loadSkills(workdir) {
362
363
  dir: path.join(os.homedir(), '.claude', 'skills'),
363
364
  source: 'global',
364
365
  });
366
+ // The skills SHIPPED with the package. In the installed layout they sit next
367
+ // to `dist/`; in a development checkout that path resolves INSIDE the repo
368
+ // (`src/../skills`), where the same skills also exist as files. The built-in
369
+ // copies (BUILTIN_SKILLS, appended below with an inline body) are the
370
+ // canonical ones — adding the files here would override them and break
371
+ // `/skill-name` expansion and the offline docs. So only add the directory when
372
+ // the module runs from the built `dist/` layout.
373
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
374
+ if (!moduleDir.endsWith(path.sep + 'src')) {
375
+ roots.push({
376
+ dir: path.join(moduleDir, '..', 'skills'),
377
+ source: 'global',
378
+ });
379
+ }
365
380
  for (const root of roots) {
366
381
  const files = await findSkillFiles(root.dir);
367
382
  for (const f of files) {
package/dist/fsutil.js CHANGED
@@ -20,15 +20,27 @@ export function writeFileAtomic(file, data) {
20
20
  // fsync the file BEFORE rename: without it a power loss can leave the
21
21
  // renamed name pointing at an empty/partial inode (rename itself is atomic,
22
22
  // but the data blocks may not have reached the disk yet).
23
- const fd = fs.openSync(tmp, 'w', 0o600);
24
23
  try {
25
- fs.writeFileSync(fd, data);
26
- fs.fsyncSync(fd);
24
+ const fd = fs.openSync(tmp, 'w', 0o600);
25
+ try {
26
+ fs.writeFileSync(fd, data);
27
+ fs.fsyncSync(fd);
28
+ }
29
+ finally {
30
+ fs.closeSync(fd);
31
+ }
32
+ fs.renameSync(tmp, file);
27
33
  }
28
- finally {
29
- fs.closeSync(fd);
34
+ catch (e) {
35
+ // A failed write/fsync/rename (full disk, EIO, a directory in the way)
36
+ // must not leave the temp file behind: every failure used to accumulate a
37
+ // stray `<file>.tmp-<pid>` next to the real file.
38
+ try {
39
+ fs.unlinkSync(tmp);
40
+ }
41
+ catch { }
42
+ throw e;
30
43
  }
31
- fs.renameSync(tmp, file);
32
44
  // fsync the DIRECTORY so the rename entry itself is durable.
33
45
  try {
34
46
  const dirFd = fs.openSync(path.dirname(file), 'r');
@@ -195,6 +195,12 @@ export function isFinishedWithoutAnswer(body) {
195
195
  }
196
196
  // True when the body carries Qwen's rate-limit / busy notice, so ask() can
197
197
  // back off instead of waiting out the deadline. Matched against the RAW body.
198
+ // The classifier runs against the RAW body, so a phrase like `please try again
199
+ // later` would also match a NORMAL answer (the model writing about retry
200
+ // logic) and make ask() back off on a good turn. A real notice arrives in an
201
+ // ERROR payload (`event: error` / `"type":"error"`) or as a code; answer text
202
+ // lives in `choices[].delta.content`. We therefore only inspect the body when
203
+ // it is NOT a plain answer stream, or when it carries no answer content.
198
204
  export function isRateLimitText(body) {
199
205
  const b = String(body || '');
200
206
  if (!b)
@@ -203,7 +209,42 @@ export function isRateLimitText(body) {
203
209
  // ("The service is currently experiencing high demand. Please try again
204
210
  // later.") — a TRANSIENT capacity limit, so it belongs with the rate-limit
205
211
  // family (back off and retry), not with the guest DAILY limit.
206
- return /too many requests|rate limit|too frequent|please try again later|quota_limit|high demand|слишком часто/i.test(b);
212
+ if (/quota_limit/i.test(b))
213
+ return true;
214
+ if (/event:\s*error|"type"\s*:\s*"error"|"error"\s*:/i.test(b)) {
215
+ return /too many requests|rate limit|too frequent|please try again later|high demand|слишком часто/i.test(b);
216
+ }
217
+ // No structured error marker: only treat the body as a notice when it has
218
+ // NO answer content (a bare notice page/JSON).
219
+ if (hasAnswerContent(b))
220
+ return false;
221
+ return /too many requests|rate limit|too frequent|please try again later|high demand|слишком часто/i.test(b);
222
+ }
223
+ // True when the body carries actual answer text in an SSE/JSON chunk.
224
+ function hasAnswerContent(body) {
225
+ for (const obj of parseDataLines(body)) {
226
+ const choices = obj.choices;
227
+ if (!Array.isArray(choices))
228
+ continue;
229
+ for (const ch of choices) {
230
+ if (!ch || typeof ch !== 'object')
231
+ continue;
232
+ const c = ch;
233
+ const delta = c.delta && typeof c.delta === 'object'
234
+ ? c.delta
235
+ : c.message && typeof c.message === 'object'
236
+ ? c.message
237
+ : null;
238
+ if (!delta)
239
+ continue;
240
+ if (isThinkingPhase(delta))
241
+ continue;
242
+ if (typeof delta.content === 'string' && delta.content.trim()) {
243
+ return true;
244
+ }
245
+ }
246
+ }
247
+ return false;
207
248
  }
208
249
  // The GUEST daily limit is a DIFFERENT failure from a transient rate limit:
209
250
  // Qwen answers `RateLimited` with "You've reached the guest chat limit for
@@ -221,5 +262,11 @@ export function isServerBusyText(body) {
221
262
  const b = String(body || '');
222
263
  if (!b)
223
264
  return false;
265
+ // Same rule as isRateLimitText: a normal ANSWER may mention "try again
266
+ // later"; only a structured error payload or a body with NO answer content
267
+ // can be a server-busy notice.
268
+ const errNotice = /event:\s*error|"type"\s*:\s*"error"|"error"\s*:/i.test(b);
269
+ if (!errNotice && hasAnswerContent(b))
270
+ return false;
224
271
  return /server is busy|service unavailable|try again later/i.test(b);
225
272
  }
package/dist/sessions.js CHANGED
@@ -46,8 +46,21 @@ export function isInternalChat(id) {
46
46
  function ensureDir() {
47
47
  fs.mkdirSync(SESSIONS_DIR, { recursive: true });
48
48
  }
49
+ // A chat id is used as a FILE NAME. Replacing every unsafe character with `_`
50
+ // made two DIFFERENT ids collide on one file ('a/b' and 'a_b'), so the second
51
+ // session overwrote the first and the other chat could not be resumed. Encode
52
+ // the unsafe characters instead: `_` becomes `_5f` and every other unsafe byte
53
+ // `_XX` (hex), which is injective and still path-safe.
49
54
  function safeName(id) {
50
- return String(id).replace(/[^a-zA-Z0-9_.-]/g, '_');
55
+ const s = String(id);
56
+ let out = '';
57
+ for (const ch of s) {
58
+ if (/[a-zA-Z0-9.-]/.test(ch))
59
+ out += ch;
60
+ else
61
+ out += '_' + ch.codePointAt(0).toString(16).padStart(2, '0');
62
+ }
63
+ return out;
51
64
  }
52
65
  // Save/update a session. workdir is needed so that when launched from the same
53
66
  // project we restore exactly its last chat.
@@ -194,7 +207,11 @@ export function loadHistory() {
194
207
  const raw = JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf-8'));
195
208
  if (!Array.isArray(raw))
196
209
  return [];
197
- return raw.filter((x) => typeof x === 'string');
210
+ const list = raw.filter((x) => typeof x === 'string');
211
+ // Cap on LOAD as well: an old/hand-edited/merged file could exceed the
212
+ // limit, and the next save would write the whole array back out. Keep the
213
+ // most recent (tail) entries, matching saveHistory().
214
+ return list.length > HISTORY_LIMIT ? list.slice(-HISTORY_LIMIT) : list;
198
215
  }
199
216
  catch {
200
217
  return [];
package/dist/web.js CHANGED
@@ -188,6 +188,26 @@ export function isPrivateIp(ip) {
188
188
  }
189
189
  if (low.startsWith('64:ff9b:'))
190
190
  return true;
191
+ // IPv4-COMPATIBLE form `::a.b.c.d` (deprecated, but the OS/HTTP stack still
192
+ // resolves it, so `http://[::127.0.0.1]/` reached localhost). `::` itself and
193
+ // `::1` are already handled above; `::ffff:…` was handled as MAPPED. Any
194
+ // other `::`-prefixed address whose tail is an embedded IPv4 belongs to the
195
+ // same family and must be judged by the embedded address.
196
+ const compat = low.match(/^::(?:ffff:)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
197
+ if (compat)
198
+ return isPrivateIp(compat[1]);
199
+ const compatHex = low.match(/^::([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
200
+ if (compatHex) {
201
+ const hi = parseInt(compatHex[1], 16);
202
+ const lo = parseInt(compatHex[2], 16);
203
+ return isPrivateIp(((hi >> 8) & 0xff) +
204
+ '.' +
205
+ (hi & 0xff) +
206
+ '.' +
207
+ ((lo >> 8) & 0xff) +
208
+ '.' +
209
+ (lo & 0xff));
210
+ }
191
211
  return false;
192
212
  }
193
213
  export function isPrivateHostname(host) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zames_pro",
3
- "version": "2.77.0",
3
+ "version": "2.78.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,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
+ "skills",
11
12
  "scripts/postinstall.mjs",
12
13
  "README.md",
13
14
  "CHANGELOG.md",
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: brainstorming
3
+ description: Use when the operator's request is a GOAL, not a spec — "add X", "make it better", "integrate Y" — and it is unclear what "done" means. Draws out the intent and plays it back before building.
4
+ ---
5
+
6
+ # Brainstorming
7
+
8
+ When a request is a goal ("add superpowers", "make scrolling feel right"),
9
+ building immediately produces the wrong thing fast. First make the intent
10
+ concrete.
11
+
12
+ ## What to pin down
13
+
14
+ - **The real outcome.** What should be TRUE after this that is not true now?
15
+ Not the feature name — the result the operator wants to see.
16
+ - **The acceptance check.** How will the operator know it works? "I open X
17
+ and Y happens." If neither of you can state this, the task is not ready.
18
+ - **Scope edges.** What is explicitly NOT part of this (the operator often
19
+ has an opinion, e.g. "no button", "don't touch the existing code").
20
+ - **The alternatives you considered** and why you chose one — a decision is
21
+ cheaper to correct before it is built.
22
+
23
+ ## The zames constraint (IMPORTANT)
24
+
25
+ zames has NO conversational turn: the operator sees only your final `respond`
26
+ and your tool calls, and a task is expected to make PROGRESS, not to ask
27
+ questions and wait. So do the brainstorming yourself and return it as a
28
+ SHORT, concrete proposal in ONE `respond`, instead of an interview:
29
+
30
+ - state the goal as you understood it;
31
+ - state your assumptions and the choices you made by default;
32
+ - state what you will NOT do;
33
+ - then either (a) proceed if the request was clearly actionable, or (b) stop
34
+ and ask the ONE decision you genuinely cannot make alone.
35
+
36
+ For a request that is already precise ("read file X", "rename Y"), skip this
37
+ entirely — brainstorming a one-line task wastes the operator's time.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: systematic-debugging
3
+ 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. Prevents guessing.
4
+ ---
5
+
6
+ # Systematic debugging
7
+
8
+ Do NOT start by editing code. Work through the four phases in order; each
9
+ phase must produce an artifact you can point at.
10
+
11
+ ## Phase 1 — Reproduce (no fix yet)
12
+
13
+ Get a command that FAILS the same way the operator describes it.
14
+
15
+ - Run it. Copy the exact error/output into your reasoning.
16
+ - If it passes, you do not have the bug yet: narrow the input until it fails,
17
+ or ask which steps differ.
18
+ - A bug you cannot reproduce is a bug you cannot verify you fixed.
19
+
20
+ ## Phase 2 — Isolate (bisect, do not read blindly)
21
+
22
+ Shrink the failure to the smallest unit that still fails.
23
+
24
+ - Comment out / stub / binary-search the input space; each step doubles the
25
+ information you have.
26
+ - Read the code that the failing path ACTUALLY executes — not the code you
27
+ remember writing.
28
+ - Check the obvious causes FIRST: wrong file, stale build, cached state,
29
+ different config, an earlier error swallowed by a catch.
30
+
31
+ ## Phase 3 — Hypothesize (one at a time)
32
+
33
+ State the hypothesis in one sentence and predict what you should see if it is
34
+ right. Then test exactly that.
35
+
36
+ - One change at a time; two changes at once prove nothing.
37
+ - Prefer an observation (a log, a probe, a test assertion) over an edit.
38
+ - If the prediction is wrong, the hypothesis is wrong — discard it, do not
39
+ "adjust" it to fit.
40
+
41
+ ## Phase 4 — Fix and verify
42
+
43
+ - Fix the CAUSE, not the symptom. A guard added to hide a wrong value leaves
44
+ the wrong value in place.
45
+ - Add a regression test that fails WITHOUT the fix and passes WITH it.
46
+ - Re-run the Phase 1 reproduction verbatim.
47
+ - Look for the same bug elsewhere (the same wrong assumption is usually not
48
+ in one place only).
49
+
50
+ ## Anti-patterns
51
+
52
+ - Editing before reproducing.
53
+ - "It works now" without knowing what changed.
54
+ - Adding a try/catch or a null-check to silence an error you do not understand.
55
+ - Trusting a green test you wrote against the FIXED code without ever seeing
56
+ it red.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: test-driven-development
3
+ description: Use when writing or changing behaviour that can be tested — a bug fix, a new function, a parser, a formatter. Write the failing test first, watch it fail, then make it pass. Prevents tests that only pass because they were written against the finished code.
4
+ ---
5
+
6
+ # Test-driven development
7
+
8
+ RED → GREEN → REFACTOR. The order is the point, not a formality.
9
+
10
+ ## The cycle
11
+
12
+ 1. **RED** — write the smallest test that expresses the new behaviour and
13
+ RUN it. It must FAIL, and fail for the RIGHT reason ("not implemented",
14
+ not a typo or a missing import). A test you never saw fail proves nothing.
15
+ 2. **GREEN** — write the MINIMUM code that makes it pass. Do not add the
16
+ next feature "while you are there"; that code is untested.
17
+ 3. **REFACTOR** — clean up with the test green, and re-run it.
18
+
19
+ ## What makes a test good
20
+
21
+ - It asserts BEHAVIOUR (input → output), not implementation detail.
22
+ - It fails when the behaviour is wrong — verify this by breaking the code
23
+ on purpose once, if you are unsure.
24
+ - It is deterministic: no sleeps, no network, no dependence on wall-clock
25
+ time or on the order other tests ran in.
26
+ - One reason to fail, with a message that says what broke.
27
+
28
+ ## Rules in this project
29
+
30
+ - A bug fix ALWAYS gets a regression test that reproduces the original bug.
31
+ - When you fix a test to make it pass, ask whether the TEST was wrong or the
32
+ CODE was wrong; do not just move the assertion.
33
+ - If the code was written before its test, delete and rewrite it driven by
34
+ the test — otherwise the test is shaped around the implementation.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: verification-before-completion
3
+ description: Use BEFORE declaring a task done or reporting success — a feature, a bug fix, a file you generated. Forces you to run the verification you promised instead of trusting that the code looks right.
4
+ ---
5
+
6
+ # Verification before completion
7
+
8
+ "It should work" is not done. Before you report success, produce EVIDENCE
9
+ from the real artifact — not from your intent.
10
+
11
+ ## The checklist
12
+
13
+ 1. **Re-read the original request.** Every requirement, including the small
14
+ ones in a parenthetical. List them; tick each one against something you
15
+ can show.
16
+ 2. **Run the thing.** For code: build/typecheck/tests. For a script: run it.
17
+ For a generated file: open/parse it and confirm it is complete, not
18
+ truncated. For a UI: the real screen, not the diff.
19
+ 3. **Check the WHOLE file.** A generated file (HTML/JSON/config) is a common
20
+ silent failure: the head is there, the body/tail is missing or a
21
+ placeholder. Confirm the closing structure exists and the file runs.
22
+ 4. **Re-read your own diff** as a reviewer: leftover debug output, dead code,
23
+ a commented-out block, a wrong path, a test that asserts nothing.
24
+ 5. **State the evidence** in the final report — the exact command and its
25
+ result — so the operator can reproduce it. "Tests pass" without the
26
+ command is not evidence.
27
+
28
+ ## The two failure modes this prevents
29
+
30
+ - **Partial delivery**: a game file with a scene but no game loop; a handler
31
+ that handles one case; a migration that covers one table. The operator runs
32
+ it, it is broken, and trust drops.
33
+ - **Phantom fix**: the code changed, but the original reproduction was never
34
+ re-run, so nothing proves the bug is gone.
35
+
36
+ ## Rule
37
+
38
+ If you cannot run the verification (no test, no runnable entry point), SAY SO
39
+ explicitly and describe exactly what you did instead. Never report "done"
40
+ while implying a verification you did not perform.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: writing-plans
3
+ description: Use when a task is large enough to need more than one or two tool calls — a multi-file change, a new feature, a refactor. Breaks the work into small ordered steps with exact paths and a verification for each.
4
+ ---
5
+
6
+ # Writing plans
7
+
8
+ A plan is a sequence of steps a careful engineer with NO project context
9
+ could follow. If a step needs knowledge you have but did not write down, the
10
+ plan is incomplete.
11
+
12
+ ## Shape of a good plan
13
+
14
+ Each step is 2–5 minutes of work and names:
15
+
16
+ - the EXACT file path(s) it touches;
17
+ - what changes (the concrete edit, not "update the parser");
18
+ - how to verify it (the command, the test name, the expected output).
19
+
20
+ Order matters: put the step that DE-RISKS the task first (a spike, a failing
21
+ test, a proof that the API works). Do not build three layers on an unverified
22
+ assumption.
23
+
24
+ ## Rules
25
+
26
+ - One concern per step. "Add the flag AND refactor the caller" is two steps.
27
+ - Prefer a step that FAILS LOUDLY over a step that silently half-works.
28
+ - Every behavioural change gets a test step (see test-driven-development);
29
+ write the failing test BEFORE the implementation step that satisfies it.
30
+ - If you discover mid-plan that a step was wrong, STOP and revise the plan —
31
+ do not improvise the rest of it.
32
+ - Keep the plan short enough to hold in your head; a 30-step plan is a sign
33
+ the task should be split.
34
+
35
+ ## In zames
36
+
37
+ Because there is no separate plan file unless the operator asks for one, the
38
+ plan lives in your reasoning and, for a long task, in a TodoWrite list — one
39
+ entry per step, status updated as you go. Do not narrate the plan to the
40
+ operator between tool calls; the todo list IS the visible plan.