@ngockhoale/ukit 1.5.23 → 1.6.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
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.5.24 - 2026-06-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Register `command-handoff-fullstack` in `manifests/platform.full.yaml` so `ukit install` now copies `handoff-fullstack.md` to target projects. Previously the template existed but was missing from the manifest.
|
|
10
|
+
|
|
5
11
|
## 1.5.23 - 2026-06-10
|
|
6
12
|
|
|
7
13
|
### Added
|
|
@@ -879,6 +879,18 @@ items:
|
|
|
879
879
|
packs:
|
|
880
880
|
- core
|
|
881
881
|
|
|
882
|
+
- id: command-handoff-fullstack
|
|
883
|
+
type: command
|
|
884
|
+
sourceTemplate: .claude/commands/ukit/handoff-fullstack.md
|
|
885
|
+
targetPath: .claude/commands/ukit/handoff-fullstack.md
|
|
886
|
+
requires:
|
|
887
|
+
- command-handoff-create
|
|
888
|
+
mergeStrategy: overwrite_with_backup
|
|
889
|
+
variables: []
|
|
890
|
+
enabledByDefault: true
|
|
891
|
+
packs:
|
|
892
|
+
- core
|
|
893
|
+
|
|
882
894
|
- id: provider-compat-config
|
|
883
895
|
type: config
|
|
884
896
|
sourceTemplate: .claude/config/providers.md
|
package/package.json
CHANGED
|
@@ -6,8 +6,9 @@ import { compactContextBlock } from './index.js';
|
|
|
6
6
|
const DEFAULT_MAX_PROMPT_ENTRIES = 12;
|
|
7
7
|
const DEFAULT_MAX_OUTPUT_ENTRIES = 12;
|
|
8
8
|
const DEFAULT_HISTORY_LAYER_COUNT = 2;
|
|
9
|
-
const SOFT_RELEASE_RATIO = 0.
|
|
9
|
+
const SOFT_RELEASE_RATIO = 0.6;
|
|
10
10
|
const HARD_RELEASE_RATIO = 0.92;
|
|
11
|
+
const COMPACT_RELEASE_TARGET = 30_000;
|
|
11
12
|
const PHASE_COOLDOWN_MS = {
|
|
12
13
|
soft: 5 * 60 * 1000,
|
|
13
14
|
hard: 10 * 60 * 1000,
|
|
@@ -414,15 +415,20 @@ function computeEstimatedTotalTokens({
|
|
|
414
415
|
recentOutputs = [],
|
|
415
416
|
estimatedContextTokens = 0,
|
|
416
417
|
baselineTokens = 0,
|
|
418
|
+
sessionTokens = 0,
|
|
417
419
|
}) {
|
|
418
420
|
const promptTokens = recentPrompts.reduce((sum, entry) => sum + finiteNumber(entry.tokens, 0), 0);
|
|
419
421
|
const outputTokens = recentOutputs.reduce((sum, entry) => sum + finiteNumber(entry.tokensBefore ?? entry.tokens, 0), 0);
|
|
420
|
-
|
|
422
|
+
const windowTokens = promptTokens + outputTokens;
|
|
423
|
+
// sessionTokens accumulates across all prompts/outputs (beyond the recent window)
|
|
424
|
+
// include only the excess over the recent window to avoid double-counting
|
|
425
|
+
const sessionExcess = Math.max(0, finiteNumber(sessionTokens, 0) - windowTokens);
|
|
426
|
+
return baselineTokens + estimatedContextTokens + windowTokens + sessionExcess;
|
|
421
427
|
}
|
|
422
428
|
|
|
423
429
|
export function buildCompactThresholds(config = {}) {
|
|
424
|
-
const softThreshold = Math.max(1, finiteNumber(config?.compact?.tokenThreshold,
|
|
425
|
-
const hardThreshold = Math.max(softThreshold + 1, Math.round(softThreshold * 1.
|
|
430
|
+
const softThreshold = Math.max(1, finiteNumber(config?.compact?.tokenThreshold, 50_000));
|
|
431
|
+
const hardThreshold = Math.max(softThreshold + 1, Math.round(softThreshold * 1.6));
|
|
426
432
|
const baselineTokens = Math.max(120, Math.min(18_000, Math.round(softThreshold * 0.18)));
|
|
427
433
|
|
|
428
434
|
return {
|
|
@@ -551,7 +557,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
551
557
|
thresholds.softThreshold = rawSoftThreshold;
|
|
552
558
|
thresholds.hardThreshold = rawHardThreshold > rawSoftThreshold
|
|
553
559
|
? rawHardThreshold
|
|
554
|
-
: Math.max(rawSoftThreshold + 1, Math.round(rawSoftThreshold * 1.
|
|
560
|
+
: Math.max(rawSoftThreshold + 1, Math.round(rawSoftThreshold * 1.6));
|
|
555
561
|
thresholds.baselineTokens = Math.max(120, Math.min(18_000, Math.round(thresholds.softThreshold * 0.18)));
|
|
556
562
|
}
|
|
557
563
|
const recentPrompts = (Array.isArray(rawState?.recentPrompts) ? rawState.recentPrompts : [])
|
|
@@ -565,6 +571,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
565
571
|
.sort((left, right) => (right.timestamp ?? 0) - (left.timestamp ?? 0))
|
|
566
572
|
.slice(0, DEFAULT_MAX_OUTPUT_ENTRIES);
|
|
567
573
|
const estimatedContextTokens = computeEstimatedContextTokens(rawState);
|
|
574
|
+
const sessionTokens = Math.max(0, finiteNumber(rawState?.sessionTokens, 0));
|
|
568
575
|
const estimatedTotalTokens = finiteNumber(
|
|
569
576
|
rawState?.estimatedTotalTokens,
|
|
570
577
|
computeEstimatedTotalTokens({
|
|
@@ -572,6 +579,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
572
579
|
recentOutputs,
|
|
573
580
|
estimatedContextTokens,
|
|
574
581
|
baselineTokens: thresholds.baselineTokens,
|
|
582
|
+
sessionTokens,
|
|
575
583
|
}),
|
|
576
584
|
);
|
|
577
585
|
const releaseThresholds = buildReleaseThresholds(thresholds);
|
|
@@ -589,6 +597,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
589
597
|
hardReleaseThreshold: releaseThresholds.hardReleaseThreshold,
|
|
590
598
|
baselineTokens: thresholds.baselineTokens,
|
|
591
599
|
estimatedContextTokens,
|
|
600
|
+
sessionTokens,
|
|
592
601
|
estimatedTotalTokens,
|
|
593
602
|
phase: resolveCompactPhase(estimatedTotalTokens, thresholds, rawState),
|
|
594
603
|
taskMode,
|
|
@@ -617,9 +626,10 @@ export function registerPromptPressure(state, {
|
|
|
617
626
|
return current;
|
|
618
627
|
}
|
|
619
628
|
|
|
629
|
+
const promptTokenCount = estimateTokenCount(normalizedPrompt);
|
|
620
630
|
const entry = normalizePromptEntry({
|
|
621
631
|
timestamp,
|
|
622
|
-
tokens:
|
|
632
|
+
tokens: promptTokenCount,
|
|
623
633
|
summary: summarizePromptText(normalizedPrompt, routingContext),
|
|
624
634
|
targetFile: routingContext?.targetFile ?? null,
|
|
625
635
|
taskType: routingContext?.taskType ?? null,
|
|
@@ -636,6 +646,7 @@ export function registerPromptPressure(state, {
|
|
|
636
646
|
updatedAt: timestamp,
|
|
637
647
|
routingContext,
|
|
638
648
|
recentPrompts,
|
|
649
|
+
sessionTokens: current.sessionTokens + promptTokenCount,
|
|
639
650
|
estimatedTotalTokens: undefined,
|
|
640
651
|
estimatedContextTokens: estimateTokenCount([
|
|
641
652
|
routeSummary?.line ?? '',
|
|
@@ -666,12 +677,13 @@ export function registerOutputPressure(state, {
|
|
|
666
677
|
return current;
|
|
667
678
|
}
|
|
668
679
|
|
|
680
|
+
const normalizedTokensBefore = Math.max(1, finiteNumber(tokensBefore, estimateTokenCount(summary || normalizedSummary)));
|
|
669
681
|
const entry = normalizeOutputEntry({
|
|
670
682
|
timestamp,
|
|
671
683
|
command,
|
|
672
684
|
profile,
|
|
673
685
|
exitCode,
|
|
674
|
-
tokensBefore:
|
|
686
|
+
tokensBefore: normalizedTokensBefore,
|
|
675
687
|
tokensAfter: Math.max(1, finiteNumber(tokensAfter, estimateTokenCount(normalizedSummary))),
|
|
676
688
|
savedTokens: Math.max(0, finiteNumber(savedTokens, 0)),
|
|
677
689
|
summary: normalizedSummary,
|
|
@@ -685,6 +697,7 @@ export function registerOutputPressure(state, {
|
|
|
685
697
|
...current,
|
|
686
698
|
updatedAt: timestamp,
|
|
687
699
|
recentOutputs,
|
|
700
|
+
sessionTokens: current.sessionTokens + normalizedTokensBefore,
|
|
688
701
|
estimatedTotalTokens: undefined,
|
|
689
702
|
taskMode: deriveTaskMode({
|
|
690
703
|
routingContext: current.routingContext ?? {},
|
|
@@ -755,9 +768,19 @@ export function registerThresholdCompactPlan(state, plan, config = {}) {
|
|
|
755
768
|
stats.softHits += 1;
|
|
756
769
|
}
|
|
757
770
|
|
|
771
|
+
// Decay sessionTokens on hard compact plan: cap at COMPACT_RELEASE_TARGET for large sessions,
|
|
772
|
+
// or fall back to recent-window tokens when already below the release target
|
|
773
|
+
const windowTokens = current.recentPrompts.reduce((sum, entry) => sum + finiteNumber(entry.tokens, 0), 0)
|
|
774
|
+
+ current.recentOutputs.reduce((sum, entry) => sum + finiteNumber(entry.tokensBefore ?? entry.tokens, 0), 0);
|
|
775
|
+
const decayTarget = current.sessionTokens > COMPACT_RELEASE_TARGET ? COMPACT_RELEASE_TARGET : windowTokens;
|
|
776
|
+
const decayedSessionTokens = normalizePhase(plan.phase) === 'hard'
|
|
777
|
+
? Math.min(current.sessionTokens, decayTarget)
|
|
778
|
+
: current.sessionTokens;
|
|
779
|
+
|
|
758
780
|
return buildCompactPressureState({
|
|
759
781
|
...current,
|
|
760
782
|
updatedAt: now,
|
|
783
|
+
sessionTokens: decayedSessionTokens,
|
|
761
784
|
cooldownUntil: now + resolvePhaseCooldownMs(plan.phase),
|
|
762
785
|
historyLayers: uniqueStrings(plan.layers ?? []).slice(0, DEFAULT_HISTORY_LAYER_COUNT),
|
|
763
786
|
latestPlan: {
|
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
const DEFAULT_MAX_PROMPT_ENTRIES = 12;
|
|
14
14
|
const DEFAULT_MAX_OUTPUT_ENTRIES = 12;
|
|
15
15
|
const DEFAULT_HISTORY_LAYER_COUNT = 2;
|
|
16
|
-
const SOFT_RELEASE_RATIO = 0.
|
|
16
|
+
const SOFT_RELEASE_RATIO = 0.6;
|
|
17
17
|
const HARD_RELEASE_RATIO = 0.92;
|
|
18
|
+
const COMPACT_RELEASE_TARGET = 30_000;
|
|
18
19
|
const PHASE_COOLDOWN_MS = {
|
|
19
20
|
soft: 5 * 60 * 1000,
|
|
20
21
|
hard: 10 * 60 * 1000,
|
|
@@ -421,15 +422,20 @@ function computeEstimatedTotalTokens({
|
|
|
421
422
|
recentOutputs = [],
|
|
422
423
|
estimatedContextTokens = 0,
|
|
423
424
|
baselineTokens = 0,
|
|
425
|
+
sessionTokens = 0,
|
|
424
426
|
}) {
|
|
425
427
|
const promptTokens = recentPrompts.reduce((sum, entry) => sum + finiteNumber(entry.tokens, 0), 0);
|
|
426
428
|
const outputTokens = recentOutputs.reduce((sum, entry) => sum + finiteNumber(entry.tokensBefore ?? entry.tokens, 0), 0);
|
|
427
|
-
|
|
429
|
+
const windowTokens = promptTokens + outputTokens;
|
|
430
|
+
// sessionTokens accumulates across all prompts/outputs (beyond the recent window)
|
|
431
|
+
// include only the excess over the recent window to avoid double-counting
|
|
432
|
+
const sessionExcess = Math.max(0, finiteNumber(sessionTokens, 0) - windowTokens);
|
|
433
|
+
return baselineTokens + estimatedContextTokens + windowTokens + sessionExcess;
|
|
428
434
|
}
|
|
429
435
|
|
|
430
436
|
export function buildCompactThresholds(config = {}) {
|
|
431
|
-
const softThreshold = Math.max(1, finiteNumber(config?.compact?.tokenThreshold,
|
|
432
|
-
const hardThreshold = Math.max(softThreshold + 1, Math.round(softThreshold * 1.
|
|
437
|
+
const softThreshold = Math.max(1, finiteNumber(config?.compact?.tokenThreshold, 50_000));
|
|
438
|
+
const hardThreshold = Math.max(softThreshold + 1, Math.round(softThreshold * 1.6));
|
|
433
439
|
const baselineTokens = Math.max(120, Math.min(18_000, Math.round(softThreshold * 0.18)));
|
|
434
440
|
|
|
435
441
|
return {
|
|
@@ -558,7 +564,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
558
564
|
thresholds.softThreshold = rawSoftThreshold;
|
|
559
565
|
thresholds.hardThreshold = rawHardThreshold > rawSoftThreshold
|
|
560
566
|
? rawHardThreshold
|
|
561
|
-
: Math.max(rawSoftThreshold + 1, Math.round(rawSoftThreshold * 1.
|
|
567
|
+
: Math.max(rawSoftThreshold + 1, Math.round(rawSoftThreshold * 1.6));
|
|
562
568
|
thresholds.baselineTokens = Math.max(120, Math.min(18_000, Math.round(thresholds.softThreshold * 0.18)));
|
|
563
569
|
}
|
|
564
570
|
const recentPrompts = (Array.isArray(rawState?.recentPrompts) ? rawState.recentPrompts : [])
|
|
@@ -572,6 +578,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
572
578
|
.sort((left, right) => (right.timestamp ?? 0) - (left.timestamp ?? 0))
|
|
573
579
|
.slice(0, DEFAULT_MAX_OUTPUT_ENTRIES);
|
|
574
580
|
const estimatedContextTokens = computeEstimatedContextTokens(rawState);
|
|
581
|
+
const sessionTokens = Math.max(0, finiteNumber(rawState?.sessionTokens, 0));
|
|
575
582
|
const estimatedTotalTokens = finiteNumber(
|
|
576
583
|
rawState?.estimatedTotalTokens,
|
|
577
584
|
computeEstimatedTotalTokens({
|
|
@@ -579,6 +586,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
579
586
|
recentOutputs,
|
|
580
587
|
estimatedContextTokens,
|
|
581
588
|
baselineTokens: thresholds.baselineTokens,
|
|
589
|
+
sessionTokens,
|
|
582
590
|
}),
|
|
583
591
|
);
|
|
584
592
|
const releaseThresholds = buildReleaseThresholds(thresholds);
|
|
@@ -596,6 +604,7 @@ export function buildCompactPressureState(rawState = null, config = {}) {
|
|
|
596
604
|
hardReleaseThreshold: releaseThresholds.hardReleaseThreshold,
|
|
597
605
|
baselineTokens: thresholds.baselineTokens,
|
|
598
606
|
estimatedContextTokens,
|
|
607
|
+
sessionTokens,
|
|
599
608
|
estimatedTotalTokens,
|
|
600
609
|
phase: resolveCompactPhase(estimatedTotalTokens, thresholds, rawState),
|
|
601
610
|
taskMode,
|
|
@@ -624,9 +633,10 @@ export function registerPromptPressure(state, {
|
|
|
624
633
|
return current;
|
|
625
634
|
}
|
|
626
635
|
|
|
636
|
+
const promptTokenCount = estimateTokenCount(normalizedPrompt);
|
|
627
637
|
const entry = normalizePromptEntry({
|
|
628
638
|
timestamp,
|
|
629
|
-
tokens:
|
|
639
|
+
tokens: promptTokenCount,
|
|
630
640
|
summary: summarizePromptText(normalizedPrompt, routingContext),
|
|
631
641
|
targetFile: routingContext?.targetFile ?? null,
|
|
632
642
|
taskType: routingContext?.taskType ?? null,
|
|
@@ -643,6 +653,7 @@ export function registerPromptPressure(state, {
|
|
|
643
653
|
updatedAt: timestamp,
|
|
644
654
|
routingContext,
|
|
645
655
|
recentPrompts,
|
|
656
|
+
sessionTokens: current.sessionTokens + promptTokenCount,
|
|
646
657
|
estimatedTotalTokens: undefined,
|
|
647
658
|
estimatedContextTokens: estimateTokenCount([
|
|
648
659
|
routeSummary?.line ?? '',
|
|
@@ -673,12 +684,13 @@ export function registerOutputPressure(state, {
|
|
|
673
684
|
return current;
|
|
674
685
|
}
|
|
675
686
|
|
|
687
|
+
const normalizedTokensBefore = Math.max(1, finiteNumber(tokensBefore, estimateTokenCount(summary || normalizedSummary)));
|
|
676
688
|
const entry = normalizeOutputEntry({
|
|
677
689
|
timestamp,
|
|
678
690
|
command,
|
|
679
691
|
profile,
|
|
680
692
|
exitCode,
|
|
681
|
-
tokensBefore:
|
|
693
|
+
tokensBefore: normalizedTokensBefore,
|
|
682
694
|
tokensAfter: Math.max(1, finiteNumber(tokensAfter, estimateTokenCount(normalizedSummary))),
|
|
683
695
|
savedTokens: Math.max(0, finiteNumber(savedTokens, 0)),
|
|
684
696
|
summary: normalizedSummary,
|
|
@@ -692,6 +704,7 @@ export function registerOutputPressure(state, {
|
|
|
692
704
|
...current,
|
|
693
705
|
updatedAt: timestamp,
|
|
694
706
|
recentOutputs,
|
|
707
|
+
sessionTokens: current.sessionTokens + normalizedTokensBefore,
|
|
695
708
|
estimatedTotalTokens: undefined,
|
|
696
709
|
taskMode: deriveTaskMode({
|
|
697
710
|
routingContext: current.routingContext ?? {},
|
|
@@ -762,9 +775,19 @@ export function registerThresholdCompactPlan(state, plan, config = {}) {
|
|
|
762
775
|
stats.softHits += 1;
|
|
763
776
|
}
|
|
764
777
|
|
|
778
|
+
// Decay sessionTokens on hard compact plan: cap at COMPACT_RELEASE_TARGET for large sessions,
|
|
779
|
+
// or fall back to recent-window tokens when already below the release target
|
|
780
|
+
const windowTokens = current.recentPrompts.reduce((sum, entry) => sum + finiteNumber(entry.tokens, 0), 0)
|
|
781
|
+
+ current.recentOutputs.reduce((sum, entry) => sum + finiteNumber(entry.tokensBefore ?? entry.tokens, 0), 0);
|
|
782
|
+
const decayTarget = current.sessionTokens > COMPACT_RELEASE_TARGET ? COMPACT_RELEASE_TARGET : windowTokens;
|
|
783
|
+
const decayedSessionTokens = normalizePhase(plan.phase) === 'hard'
|
|
784
|
+
? Math.min(current.sessionTokens, decayTarget)
|
|
785
|
+
: current.sessionTokens;
|
|
786
|
+
|
|
765
787
|
return buildCompactPressureState({
|
|
766
788
|
...current,
|
|
767
789
|
updatedAt: now,
|
|
790
|
+
sessionTokens: decayedSessionTokens,
|
|
768
791
|
cooldownUntil: now + resolvePhaseCooldownMs(plan.phase),
|
|
769
792
|
historyLayers: uniqueStrings(plan.layers ?? []).slice(0, DEFAULT_HISTORY_LAYER_COUNT),
|
|
770
793
|
latestPlan: {
|