@commonlyai/cli 0.1.87 → 0.1.88

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.87",
3
+ "version": "0.1.88",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -483,12 +483,53 @@ export const createClaimKeeper = (client, {
483
483
 
484
484
  // ── post-time length gate ───────────────────────────────────────────────────
485
485
 
486
+ // A sentence ends at [.!?] followed by whitespace, except when what precedes
487
+ // the whitespace is only a numbered list marker at the start of the line: in
488
+ // "3. Run it once." the first period belongs to the marker, not a sentence.
489
+ // Applied to one line at a time, so `^` is the line start.
490
+ const SENTENCE_END = /(?<=[.!?])(?<!^[ \t]*\d{1,3}\.)\s+/;
491
+
492
+ // Split one over-limit line at sentence boundaries, and a sentence that is
493
+ // itself over the limit at word boundaries. A single over-limit word (a URL)
494
+ // posts whole rather than being cut.
495
+ const splitLineForChat = (line, limit) => {
496
+ const units = [];
497
+ let piece = '';
498
+ const flushPiece = () => {
499
+ if (piece.trim()) units.push(piece.trim());
500
+ piece = '';
501
+ };
502
+ for (const sentence of line.split(SENTENCE_END)) {
503
+ if (sentence.length > limit) {
504
+ flushPiece();
505
+ let run = '';
506
+ for (const word of sentence.split(/\s+/)) {
507
+ if (run && `${run} ${word}`.length > limit) {
508
+ units.push(run);
509
+ run = word;
510
+ } else {
511
+ run = run ? `${run} ${word}` : word;
512
+ }
513
+ }
514
+ if (run) units.push(run);
515
+ } else if (piece && `${piece} ${sentence}`.length > limit) {
516
+ flushPiece();
517
+ piece = sentence;
518
+ } else {
519
+ piece = piece ? `${piece} ${sentence}` : sentence;
520
+ }
521
+ }
522
+ flushPiece();
523
+ return units;
524
+ };
525
+
486
526
  /**
487
527
  * Split chat text into tone-contract-sized messages without ever cutting
488
528
  * content. Boundaries in preference order: fenced code blocks stay whole
489
529
  * (atomic — an oversized fence becomes one oversized message rather than a
490
- * broken pair), then paragraphs, then sentences, then words. Greedy packing
491
- * rejoins small pieces so two short paragraphs share one message.
530
+ * broken pair), then paragraphs, then lines (a list item never leaves its
531
+ * marker), then sentences, then words. Greedy packing rejoins small pieces so
532
+ * two short paragraphs share one message.
492
533
  */
493
534
  export const splitForChat = (text, { limit = 400 } = {}) => {
494
535
  const trimmed = String(text || '').trim();
@@ -529,39 +570,39 @@ export const splitForChat = (text, { limit = 400 } = {}) => {
529
570
  }
530
571
  flushProse();
531
572
 
532
- // Pass 2: split oversized prose blocks at sentence then word boundaries.
573
+ // Pass 2: split oversized prose blocks at line, then sentence, then word
574
+ // boundaries. Lines come first because a list is one paragraph block whose
575
+ // items are separated by single newlines: splitting it by sentence read a
576
+ // numbered marker ("3.") as a sentence end and packed it onto the previous
577
+ // piece, so a support reply posted one message ending in "3." and the next
578
+ // opening with step 3's body (hq-support, 2026-10-09). Lines pack greedily
579
+ // with single newlines, so a list stays a list inside a message; only a line
580
+ // that is itself over the limit goes on to sentences and words.
533
581
  const units = [];
534
582
  for (const block of blocks) {
535
583
  if (block.atomic || block.text.length <= limit) {
536
584
  units.push(block.text);
537
585
  continue;
538
586
  }
539
- let piece = '';
540
- const flushPiece = () => {
541
- if (piece.trim()) units.push(piece.trim());
542
- piece = '';
587
+ let group = '';
588
+ const flushGroup = () => {
589
+ if (group) units.push(group);
590
+ group = '';
543
591
  };
544
- for (const sentence of block.text.split(/(?<=[.!?])\s+/)) {
545
- if (sentence.length > limit) {
546
- flushPiece();
547
- let run = '';
548
- for (const word of sentence.split(/\s+/)) {
549
- if (run && `${run} ${word}`.length > limit) {
550
- units.push(run);
551
- run = word;
552
- } else {
553
- run = run ? `${run} ${word}` : word;
554
- }
555
- }
556
- if (run) units.push(run); // a single over-limit word (URL) posts whole
557
- } else if (piece && `${piece} ${sentence}`.length > limit) {
558
- flushPiece();
559
- piece = sentence;
592
+ for (const rawLine of block.text.split('\n')) {
593
+ const line = rawLine.trimEnd(); // keep a nested item's indentation
594
+ if (!line.trim()) continue;
595
+ if (line.length > limit) {
596
+ flushGroup();
597
+ units.push(...splitLineForChat(line, limit));
598
+ } else if (group && `${group}\n${line}`.length > limit) {
599
+ flushGroup();
600
+ group = line;
560
601
  } else {
561
- piece = piece ? `${piece} ${sentence}` : sentence;
602
+ group = group ? `${group}\n${line}` : line;
562
603
  }
563
604
  }
564
- flushPiece();
605
+ flushGroup();
565
606
  }
566
607
 
567
608
  // Pass 3: greedy packing back up to the limit.