kairos-chain 3.42.0 → 3.42.1
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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d6b5abadc2727f37c441c8d0442da6470438537ad47f387e6c4021769c678dd
|
|
4
|
+
data.tar.gz: f5c625947d73fb41251829cf638b397308294daa4fed7ed94cae945ad6086b72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4948c9a12280c2ccc7e0083e056d0ad99d457fcb11d86761f060a891d2033924395521364b5f867ca30eec2cc806f5d539ccc22282657c31e31a0695a5270f55
|
|
7
|
+
data.tar.gz: 789de8f265c7d1dbbde4969643e26f3359887b3417ddd76fe5567348b41c9dc8c37b2f062f6e77d382abc49becd4a2703ac4de29cf8d2f63b62c9fef272a5db1
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [3.42.1] - 2026-07-14
|
|
8
|
+
|
|
9
|
+
### Fixed — SectionWriter auto-chunk completeness & duplicate tail
|
|
10
|
+
|
|
11
|
+
Two follow-ups to the v3.42.0 auto-chunk on output-uncapped providers
|
|
12
|
+
(e.g. the `claude_code` CLI):
|
|
13
|
+
|
|
14
|
+
- **Completeness.** v3.42.0 added a per-chunk "rewrite ONLY these paragraphs"
|
|
15
|
+
bound to avoid a trailing duplicate. On `claude_code` that bound backfired —
|
|
16
|
+
the model under-produced and dropped the section tail, leaving long sections
|
|
17
|
+
incomplete. Removed the bound.
|
|
18
|
+
- **Duplicate tail (provider-agnostic).** Some providers "finish the section"
|
|
19
|
+
from the first chunk; the remaining passes then appended duplicate content.
|
|
20
|
+
`SectionWriter` now stops chunking once the assembled output already covers
|
|
21
|
+
~the whole source (`section_complete_fraction`, default 0.85). Providers that
|
|
22
|
+
rewrite each chunk faithfully (e.g. API adapters, where `max_tokens` is
|
|
23
|
+
honoured) never trip this and still run every chunk. Net: a single clean,
|
|
24
|
+
complete section on both provider kinds. `chunks` in the result now reports
|
|
25
|
+
the number of passes actually run.
|
|
26
|
+
|
|
7
27
|
## [3.42.0] - 2026-07-14
|
|
8
28
|
|
|
9
29
|
### Fixed / Improved — agent SkillSet self-drive on long document sections
|
data/lib/kairos_mcp/version.rb
CHANGED
|
@@ -23,20 +23,27 @@ module KairosMcp
|
|
|
23
23
|
# paragraph boundaries and generate each chunk in its own pass, appending the
|
|
24
24
|
# results. Short contexts stay a single pass (chunks == [context_text]).
|
|
25
25
|
chunks = split_context(context_text, chunk_target)
|
|
26
|
+
source_chars = context_text.to_s.gsub(/\s+/, '').length
|
|
27
|
+
complete_at = (source_chars * complete_fraction).ceil
|
|
26
28
|
|
|
27
29
|
total_units = 0
|
|
28
30
|
total_chars = 0
|
|
31
|
+
chunks_written = 0
|
|
29
32
|
chunks.each_with_index do |chunk, idx|
|
|
30
33
|
first = idx.zero?
|
|
34
|
+
# Provider-agnostic de-duplication: some output-uncapped providers (e.g. the
|
|
35
|
+
# claude_code CLI) "finish the section" from the first chunk. Once the assembled
|
|
36
|
+
# output already covers ~the whole source, stop — further passes would only
|
|
37
|
+
# append duplicate content. Providers that rewrite each chunk faithfully never
|
|
38
|
+
# trip this (their per-chunk output tracks per-chunk input), so all chunks run.
|
|
39
|
+
break if !first && total_chars >= complete_at
|
|
40
|
+
|
|
41
|
+
# Continuation chunks must not repeat the section heading. (A stronger
|
|
42
|
+
# "rewrite only these paragraphs" bound was tried but backfired on the
|
|
43
|
+
# claude_code CLI — it then under-produced and dropped the section tail.)
|
|
31
44
|
chunk_instructions =
|
|
32
|
-
if chunks.length > 1
|
|
33
|
-
#
|
|
34
|
-
# "finish the section" (e.g. claude_code) regenerate the whole section from
|
|
35
|
-
# the first chunk, so later chunks duplicate content. Continuation chunks
|
|
36
|
-
# also must not repeat the heading.
|
|
37
|
-
notes = [bounded_note(language)]
|
|
38
|
-
notes << continuation_note(language) unless first
|
|
39
|
-
"#{instructions}\n\n#{notes.join("\n")}"
|
|
45
|
+
if chunks.length > 1 && !first
|
|
46
|
+
"#{instructions}\n\n#{continuation_note(language)}"
|
|
40
47
|
else
|
|
41
48
|
instructions
|
|
42
49
|
end
|
|
@@ -49,6 +56,7 @@ module KairosMcp
|
|
|
49
56
|
write_chunk(output_file, gen['content'], first ? append_mode : true)
|
|
50
57
|
total_units += count_units(gen['content'], language)
|
|
51
58
|
total_chars += gen['content'].gsub(/\s+/, '').length
|
|
59
|
+
chunks_written += 1
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
{
|
|
@@ -60,7 +68,7 @@ module KairosMcp
|
|
|
60
68
|
# non-whitespace char count so callers never size output from a bogus metric.
|
|
61
69
|
'word_count' => total_units,
|
|
62
70
|
'char_count' => total_chars,
|
|
63
|
-
'chunks' =>
|
|
71
|
+
'chunks' => chunks_written
|
|
64
72
|
}
|
|
65
73
|
end
|
|
66
74
|
|
|
@@ -167,24 +175,20 @@ module KairosMcp
|
|
|
167
175
|
end
|
|
168
176
|
|
|
169
177
|
# Auto-chunk threshold. Sized so each pass is small enough that output-uncapped
|
|
170
|
-
# providers (e.g. claude_code CLI) emit
|
|
178
|
+
# providers (e.g. claude_code CLI) emit a full section without stopping early —
|
|
171
179
|
# empirically a larger single pass truncates, while ~3000-char passes complete.
|
|
172
|
-
#
|
|
180
|
+
# On such providers the first chunk may "finish the section", so the output can
|
|
181
|
+
# carry a trailing duplicate fragment; completeness is favoured over that cosmetic
|
|
182
|
+
# cost. API providers (with max_tokens honoured) produce a clean single section.
|
|
183
|
+
# Overridable via config.
|
|
173
184
|
def chunk_target
|
|
174
185
|
(@config['section_chunk_target_chars'] || 3000).to_i
|
|
175
186
|
end
|
|
176
187
|
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
'重要: 以下に与えた段落だけを推敲せよ。節はいくつかに分けて処理される。' \
|
|
182
|
-
'与えられていない段落を書き足したり、節の続きをここで完成させたりしてはならない。'
|
|
183
|
-
else
|
|
184
|
-
'IMPORTANT: Rewrite ONLY the paragraphs given below. The section is processed ' \
|
|
185
|
-
'in several passes; do NOT add paragraphs that are not in this input or ' \
|
|
186
|
-
'finish the rest of the section here.'
|
|
187
|
-
end
|
|
188
|
+
# Fraction of the source (by non-whitespace chars) at which the assembled output is
|
|
189
|
+
# considered to already cover the whole section, so remaining chunks are skipped.
|
|
190
|
+
def complete_fraction
|
|
191
|
+
(@config['section_complete_fraction'] || 0.85).to_f
|
|
188
192
|
end
|
|
189
193
|
|
|
190
194
|
# Appended to a continuation chunk's instructions so the model does not repeat the
|
|
@@ -468,6 +468,40 @@ assert("T18g: append_mode applies only to the first chunk; later chunks append")
|
|
|
468
468
|
content.start_with?('PRE') && content.include?("c1\n\nc2\n\nc3")
|
|
469
469
|
end
|
|
470
470
|
|
|
471
|
+
assert("T18h: stops chunking once output already covers the whole source (no duplicate tail)") do
|
|
472
|
+
caller = MockCallerTool.new
|
|
473
|
+
calls = 0
|
|
474
|
+
caller.define_singleton_method(:invoke_tool) do |tool_name, arguments = {}, context: nil|
|
|
475
|
+
calls += 1
|
|
476
|
+
# First pass "finishes the section" (output >= source); a later pass would be a dup.
|
|
477
|
+
body = calls == 1 ? ('な' * 500) : 'DUPLICATE'
|
|
478
|
+
[{ text: JSON.generate({ 'status' => 'ok', 'response' => { 'content' => body }, 'snapshot' => {} }) }]
|
|
479
|
+
end
|
|
480
|
+
writer = SW.new(caller, { 'section_chunk_target_chars' => 100 })
|
|
481
|
+
ctx = (1..5).map { ('あ' * 80) }.join("\n\n") # ~400 non-ws source chars, would be 5 chunks
|
|
482
|
+
out = File.join(TMPDIR, 'earlystop.md')
|
|
483
|
+
r = writer.write(section_name: 's', instructions: 'i', context_text: ctx,
|
|
484
|
+
output_file: out, max_words: 100, language: 'ja', append_mode: false)
|
|
485
|
+
# chunk1 output (500) >= 0.85 * 400 = 340 → stop after the first pass
|
|
486
|
+
r['chunks'] == 1 && calls == 1 && !File.read(out).include?('DUPLICATE')
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
assert("T18i: faithful per-chunk provider still runs all chunks (no premature stop)") do
|
|
490
|
+
caller = MockCallerTool.new
|
|
491
|
+
calls = 0
|
|
492
|
+
caller.define_singleton_method(:invoke_tool) do |tool_name, arguments = {}, context: nil|
|
|
493
|
+
calls += 1
|
|
494
|
+
[{ text: JSON.generate({ 'status' => 'ok', 'response' => { 'content' => "p#{calls}" }, 'snapshot' => {} }) }]
|
|
495
|
+
end
|
|
496
|
+
writer = SW.new(caller, { 'section_chunk_target_chars' => 100 })
|
|
497
|
+
ctx = (1..4).map { ('あ' * 80) }.join("\n\n")
|
|
498
|
+
out = File.join(TMPDIR, 'faithful.md')
|
|
499
|
+
r = writer.write(section_name: 's', instructions: 'i', context_text: ctx,
|
|
500
|
+
output_file: out, max_words: 100, language: 'ja', append_mode: false)
|
|
501
|
+
# short per-chunk output never reaches the completeness threshold → all chunks run
|
|
502
|
+
r['chunks'] > 1 && calls == r['chunks'] && File.read(out).include?("p#{calls}")
|
|
503
|
+
end
|
|
504
|
+
|
|
471
505
|
assert("T18d: max_tokens respects config ceiling override") do
|
|
472
506
|
caller = MockCallerTool.new
|
|
473
507
|
captured = {}
|