blun-king-cli 9.1.427 → 9.1.428

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.
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+
5
+ const JAVASCRIPT_LIKE_EXTENSIONS = new Set([
6
+ '.cjs',
7
+ '.cts',
8
+ '.js',
9
+ '.jsx',
10
+ '.mjs',
11
+ '.mts',
12
+ '.ts',
13
+ '.tsx',
14
+ ]);
15
+ const DEFAULT_MIN_REPEATS = 8;
16
+
17
+ function findDegenerateGeneratedStatementRun(filePath, content, options = {}) {
18
+ if (!JAVASCRIPT_LIKE_EXTENSIONS.has(path.extname(filePath).toLowerCase())) return null;
19
+ if (typeof content !== 'string' || content.length === 0) return null;
20
+
21
+ const minRepeats = Number.isSafeInteger(options.minRepeats) && options.minRepeats >= 2
22
+ ? options.minRepeats
23
+ : DEFAULT_MIN_REPEATS;
24
+ let state = 'code';
25
+ let quote = '';
26
+ let escaped = false;
27
+ let line = 1;
28
+ let statementStart = 0;
29
+ let statementStartLine = 1;
30
+ let parenDepth = 0;
31
+ let bracketDepth = 0;
32
+ let previous = null;
33
+ let runCount = 0;
34
+ let runLine = 1;
35
+
36
+ const resetBoundary = (nextStart) => {
37
+ statementStart = nextStart;
38
+ statementStartLine = line;
39
+ };
40
+ const recordStatement = (end) => {
41
+ const raw = content.slice(statementStart, end);
42
+ const rawStartLine = statementStartLine;
43
+ resetBoundary(end);
44
+ const leading = raw.match(/^\s*/u)?.[0] ?? '';
45
+ const statementLine = rawStartLine + (leading.match(/\n/gu)?.length ?? 0);
46
+ const normalized = raw.replace(/\s+/gu, ' ').trim();
47
+ if (normalized.length === 0 || !/[A-Za-z_$]/u.test(normalized)) {
48
+ previous = null;
49
+ runCount = 0;
50
+ return null;
51
+ }
52
+ if (normalized === previous) runCount += 1;
53
+ else {
54
+ previous = normalized;
55
+ runCount = 1;
56
+ runLine = statementLine;
57
+ }
58
+ if (runCount < minRepeats) return null;
59
+ return {
60
+ count: runCount,
61
+ line: runLine,
62
+ preview: normalized.slice(0, 160),
63
+ };
64
+ };
65
+
66
+ for (let index = 0; index < content.length; index += 1) {
67
+ const current = content[index] ?? '';
68
+ const next = content[index + 1] ?? '';
69
+
70
+ if (state === 'line-comment') {
71
+ if (current === '\n') {
72
+ line += 1;
73
+ state = 'code';
74
+ }
75
+ continue;
76
+ }
77
+ if (state === 'block-comment') {
78
+ if (current === '\n') line += 1;
79
+ if (current === '*' && next === '/') {
80
+ state = 'code';
81
+ index += 1;
82
+ }
83
+ continue;
84
+ }
85
+ if (state === 'quote') {
86
+ if (current === '\n') line += 1;
87
+ if (escaped) {
88
+ escaped = false;
89
+ continue;
90
+ }
91
+ if (current === '\\') {
92
+ escaped = true;
93
+ continue;
94
+ }
95
+ if (current === quote) {
96
+ state = 'code';
97
+ quote = '';
98
+ }
99
+ continue;
100
+ }
101
+
102
+ if (current === '\n') {
103
+ line += 1;
104
+ continue;
105
+ }
106
+ if (current === '/' && next === '/') {
107
+ state = 'line-comment';
108
+ index += 1;
109
+ continue;
110
+ }
111
+ if (current === '/' && next === '*') {
112
+ state = 'block-comment';
113
+ index += 1;
114
+ continue;
115
+ }
116
+ if (current === "'" || current === '"' || current === '`') {
117
+ state = 'quote';
118
+ quote = current;
119
+ escaped = false;
120
+ continue;
121
+ }
122
+ if (current === '(') parenDepth += 1;
123
+ else if (current === ')') parenDepth = Math.max(0, parenDepth - 1);
124
+ else if (current === '[') bracketDepth += 1;
125
+ else if (current === ']') bracketDepth = Math.max(0, bracketDepth - 1);
126
+ else if ((current === '{' || current === '}') && parenDepth === 0 && bracketDepth === 0) {
127
+ previous = null;
128
+ runCount = 0;
129
+ resetBoundary(index + 1);
130
+ } else if (current === ';' && parenDepth === 0 && bracketDepth === 0) {
131
+ const finding = recordStatement(index + 1);
132
+ if (finding !== null) return finding;
133
+ }
134
+ }
135
+
136
+ return null;
137
+ }
138
+
139
+ module.exports = {
140
+ DEFAULT_MIN_REPEATS,
141
+ findDegenerateGeneratedStatementRun,
142
+ };
package/blun.mjs CHANGED
@@ -253564,7 +253564,7 @@ function replaceOnceLiteral(content, oldString, newString) {
253564
253564
  if (index === -1) return content;
253565
253565
  return content.slice(0, index) + newString + content.slice(index + oldString.length);
253566
253566
  }
253567
- var EditInputSchema, EditTool;
253567
+ var EditInputSchema, EditTool, findDegenerateGeneratedStatementRun;
253568
253568
  var init_edit = __esmMin((() => {
253569
253569
  init_zod$1();
253570
253570
  init_tool_access();
@@ -253575,6 +253575,7 @@ var init_edit = __esmMin((() => {
253575
253575
  init_source_file_line_limit();
253576
253576
  init_edit$1();
253577
253577
  init_lsp_diagnostics();
253578
+ ({ findDegenerateGeneratedStatementRun } = createRequire(import.meta.url)("./bin/generated-source-health.cjs"));
253578
253579
  EditInputSchema = object({
253579
253580
  path: string().describe("Path to the text file to edit. Relative paths resolve against the working directory; a path outside the working directory must be absolute."),
253580
253581
  old_string: string().min(1).describe("Exact content to replace from the Read output view, without the line-number prefix. Use LF for pure CRLF files; use actual \\r escapes where Read shows \\r."),
@@ -253582,7 +253583,7 @@ var init_edit = __esmMin((() => {
253582
253583
  replace_all: boolean$1().optional().describe("Set true only when every occurrence of old_string should be replaced."),
253583
253584
  single_file_override: boolean$1().optional().describe("Set true only when the latest direct user message explicitly requires one single source file. The tool verifies that request and reports the override visibly.")
253584
253585
  });
253585
- edit_default = "Perform exact replacements in existing files. Use Edit for every incremental change instead of Write or Bash `sed`. Read the target immediately before every Edit; take `old_string` and `new_string` from that current Read output, remove the line-number prefix and tab, and never guess from memory.\n\n`old_string` must be exact and unique unless `replace_all` is true. Add surrounding context when it is ambiguous. Use `replace_all` only when every occurrence should change, and keep `new_string` different from `old_string`. Parallel Edit calls may target different files only. Before another Edit on the same file, read it again because the first replacement can invalidate the next `old_string`.\n\nFor pure CRLF files, Read shows LF; use LF in both strings and Edit restores CRLF. For mixed endings or lone carriage returns, include the displayed `\\r` escapes exactly.\n\nSource files may contain at most 500 lines. An already oversized file may only be edited when the result has fewer lines. Set `single_file_override=true` only when the latest direct user message explicitly requires one single file; the accepted override is shown to the user.";
253586
+ edit_default = "Perform exact replacements in existing files. Use Edit for every incremental change instead of Write or Bash `sed`. Read the target immediately before every Edit; take `old_string` and `new_string` from that current Read output, remove the line-number prefix and tab, and never guess from memory.\n\n`old_string` must be exact and unique unless `replace_all` is true. Add surrounding context when it is ambiguous. Use `replace_all` only when every occurrence should change, and keep `new_string` different from `old_string`. Parallel Edit calls may target different files only. Before another Edit on the same file, read it again because the first replacement can invalidate the next `old_string`.\n\nFor pure CRLF files, Read shows LF; use LF in both strings and Edit restores CRLF. For mixed endings or lone carriage returns, include the displayed `\\r` escapes exactly.\n\nSource files may contain at most 500 lines. An already oversized file may only be edited when the result has fewer lines. Set `single_file_override=true` only when the latest direct user message explicitly requires one single file; the accepted override is shown to the user. Runaway repeated JavaScript or TypeScript statements are rejected before file I/O.";
253586
253587
  EditTool = class {
253587
253588
  kaos;
253588
253589
  workspace;
@@ -253627,6 +253628,11 @@ var init_edit = __esmMin((() => {
253627
253628
  isError: true,
253628
253629
  output: "No changes to make: old_string and new_string are exactly the same."
253629
253630
  };
253631
+ const degenerateStatementRun = findDegenerateGeneratedStatementRun(safePath, args.new_string);
253632
+ if (degenerateStatementRun !== null) return {
253633
+ isError: true,
253634
+ output: `Refused to edit ${args.path}: generated source contains a runaway repeated statement ${String(degenerateStatementRun.count)} times from line ${String(degenerateStatementRun.line)} (starts with ${JSON.stringify(degenerateStatementRun.preview)}). Regenerate that section in a smaller focused chunk. No file was written.`
253635
+ };
253630
253636
  try {
253631
253637
  const raw = await this.kaos.readText(safePath);
253632
253638
  const modelView = toModelTextView(raw);
@@ -259815,7 +259821,7 @@ var init_write$1 = __esmMin((() => {
259815
259821
  }));
259816
259822
  //#endregion
259817
259823
  write_default = "Create, append to, or completely replace a file. Missing parent directories are created automatically. Overwrite is the default; append adds content at EOF without adding a newline.\n\nUse Write for a new file or a complete replacement. For every incremental change to an existing file, Use Edit instead, even when it is small. Read before overwriting an existing file. Do not create unsolicited documentation, README, summary, or report files unless the user or project instructions require them.\n\nContent is written literally. Never include Read/Edit line prefixes. Supplied LF and CRLF endings are preserved. Source files may contain at most 500 lines; split larger implementations into focused files. Set `single_file_override=true` only when the latest direct user message explicitly requires one source file.\n\nWrite complete files up to 4,096 UTF-8 bytes atomically. For a larger new or completely replaced file, use `continuation`. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Set exact `expected_lines` and, when available, `expected_sha256`. Part 1 uses overwrite, `part=1`, and `start_line=1`; every later part uses append, the next consecutive part, and the exact `next_start_line` returned by Write. Set `final=true` only on the complete final part. The Target file remains unchanged until final line-count and optional SHA-256 checks pass. After an oversized plain Write is refused, that path accepts only continuation calls until the sequence completes. Restart an unfinished sequence only with part 1, overwrite, `start_line=1`, and `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
259818
- write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved. Large files and source files over 500 lines are accepted natively: the runtime fragments them on disk, assembles them through atomic replacement, and verifies the persisted SHA-256. No manual continuation calls or `single_file_override` are required.\n\nThe explicit `continuation` protocol remains available for streamed generation across several tool calls. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Optionally set exact `expected_lines` and, when available, `expected_sha256`. Part number, start line, and mode are derived from actually staged content when omitted. Set `final=true` only on the complete final part. The target file remains unchanged until final line-count and optional SHA-256 checks pass. Restart an unfinished sequence with `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
259824
+ write_default = "Create, append to, or replace a whole file. Missing parent directories are created automatically. Overwrite is default; append adds content at EOF without a newline.\n\nUse Write only for new files or complete replacements. Use Edit for every incremental change, even a small one. Read before overwriting an existing file. Do not create unsolicited documentation unless the user or project instructions require it.\n\nContent is literal. Never include line prefixes. Supplied LF and CRLF endings are preserved. Large files and source files over 500 lines are accepted natively: the runtime fragments them on disk, assembles them through atomic replacement, and verifies the persisted SHA-256. No manual continuation calls or `single_file_override` are required.\n\nThe explicit `continuation` protocol remains available for streamed generation across several tool calls. Keep each chunk within the configured UTF-8 bytes limit and end it on a complete line with `\\n`. Optionally set exact `expected_lines` and, when available, `expected_sha256`. Part number, start line, and mode are derived from actually staged content when omitted. Set `final=true` only on the complete final part. The target file remains unchanged until final line-count and optional SHA-256 checks pass. Restart an unfinished sequence with `reset=true`. Never use continuation for incremental edits.\n\nRunaway generated JavaScript or TypeScript identifiers and repeated executable statements are rejected before disk I/O; regenerate only the affected section as a smaller chunk.";
259819
259825
  //#region ../../packages/agent-core/src/tools/builtin/file/generated-source-health.ts
259820
259826
  /**
259821
259827
  * Find runaway generated identifiers while ignoring comments and strings.
@@ -259913,6 +259919,7 @@ function isIdentifierPart(value) {
259913
259919
  var JAVASCRIPT_LIKE_EXTENSIONS;
259914
259920
  var init_generated_source_health = __esmMin((() => {
259915
259921
  init_dist$6();
259922
+ ({ findDegenerateGeneratedStatementRun } = createRequire(import.meta.url)("./bin/generated-source-health.cjs"));
259916
259923
  JAVASCRIPT_LIKE_EXTENSIONS = new Set([
259917
259924
  ".cjs",
259918
259925
  ".cts",
@@ -260155,6 +260162,11 @@ bytesWritten: number$1().int().nonnegative() });
260155
260162
  isError: true,
260156
260163
  output: `Refused to write ${args.path}: generated source contains a runaway ${String(degenerateIdentifier.length)}-character identifier on line ${String(degenerateIdentifier.line)} (starts with ${JSON.stringify(degenerateIdentifier.preview)}). Regenerate that section in a smaller focused chunk. No file was written.`
260157
260164
  };
260165
+ const degenerateStatementRun = findDegenerateGeneratedStatementRun(safePath, args.content);
260166
+ if (degenerateStatementRun !== null) return {
260167
+ isError: true,
260168
+ output: `Refused to write ${args.path}: generated source contains a runaway repeated statement ${String(degenerateStatementRun.count)} times from line ${String(degenerateStatementRun.line)} (starts with ${JSON.stringify(degenerateStatementRun.preview)}). Regenerate that section in a smaller focused chunk. No file was written.`
260169
+ };
260158
260170
  let currentContent;
260159
260171
  if (isSourceFilePath(safePath)) try {
260160
260172
  currentContent = await this.kaos.readText(safePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.427",
3
+ "version": "9.1.428",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {