@kitsy/coop-core 2.2.3 → 2.2.5

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/dist/index.cjs CHANGED
@@ -3704,6 +3704,23 @@ function clone_ledger2(ledger) {
3704
3704
  function normalize_track3(track) {
3705
3705
  return (track ?? "unassigned").trim().toLowerCase();
3706
3706
  }
3707
+ function promotion_timestamp(task, track) {
3708
+ if (!task.promoted_at) {
3709
+ return 0;
3710
+ }
3711
+ const promotedTrack = task.promoted_track?.trim();
3712
+ const scopedTrack = track?.trim();
3713
+ if (promotedTrack) {
3714
+ if (!scopedTrack) {
3715
+ return 0;
3716
+ }
3717
+ if (normalize_track3(promotedTrack) !== normalize_track3(scopedTrack)) {
3718
+ return 0;
3719
+ }
3720
+ }
3721
+ const parsed = Date.parse(task.promoted_at);
3722
+ return Number.isNaN(parsed) ? 0 : parsed;
3723
+ }
3707
3724
  function task_matches_track(task, track) {
3708
3725
  const normalized = normalize_track3(track);
3709
3726
  if (normalize_track3(task.track) === normalized) {
@@ -3809,6 +3826,11 @@ function schedule_next(graph, options = {}) {
3809
3826
  fits_wip: true
3810
3827
  }));
3811
3828
  scored.sort((a, b) => {
3829
+ const promotionA = promotion_timestamp(a.task, options.track);
3830
+ const promotionB = promotion_timestamp(b.task, options.track);
3831
+ if (promotionA !== promotionB) {
3832
+ return promotionB - promotionA;
3833
+ }
3812
3834
  if (a.score !== b.score) return b.score - a.score;
3813
3835
  return a.task.id.localeCompare(b.task.id);
3814
3836
  });
@@ -4765,6 +4787,10 @@ function isIsoDate(value) {
4765
4787
  }
4766
4788
  return date.toISOString().slice(0, 10) === value;
4767
4789
  }
4790
+ function isIsoDateTime(value) {
4791
+ const date = new Date(value);
4792
+ return !Number.isNaN(date.valueOf());
4793
+ }
4768
4794
  function validateStringArrayField(errors, field, value, options = {}) {
4769
4795
  if (value === void 0) {
4770
4796
  return;
@@ -4891,6 +4917,28 @@ function validateStructural(task, context = {}) {
4891
4917
  validateStringArrayField(errors, "fix_versions", task.fix_versions);
4892
4918
  validateStringArrayField(errors, "released_in", task.released_in);
4893
4919
  validatePriorityContext(errors, task.priority_context);
4920
+ if (task.promoted_at !== void 0) {
4921
+ if (typeof task.promoted_at !== "string" || !isIsoDateTime(task.promoted_at)) {
4922
+ errors.push(
4923
+ error4(
4924
+ "promoted_at",
4925
+ "struct.promoted_at_iso_datetime",
4926
+ "Field 'promoted_at' must be an ISO datetime string."
4927
+ )
4928
+ );
4929
+ }
4930
+ }
4931
+ if (task.promoted_track !== void 0 && task.promoted_track !== null) {
4932
+ if (typeof task.promoted_track !== "string" || task.promoted_track.trim().length === 0) {
4933
+ errors.push(
4934
+ error4(
4935
+ "promoted_track",
4936
+ "struct.promoted_track_string",
4937
+ "Field 'promoted_track' must be a non-empty string or null."
4938
+ )
4939
+ );
4940
+ }
4941
+ }
4894
4942
  if (task.story_points !== void 0 && (!Number.isFinite(task.story_points) || task.story_points < 0)) {
4895
4943
  errors.push(
4896
4944
  error4(
package/dist/index.d.cts CHANGED
@@ -305,6 +305,8 @@ interface TaskPlanning {
305
305
  track?: string;
306
306
  delivery_tracks?: string[];
307
307
  priority_context?: Record<string, TaskPriority>;
308
+ promoted_at?: string;
309
+ promoted_track?: string | null;
308
310
  assignee?: string | null;
309
311
  depends_on?: string[];
310
312
  tags?: string[];
package/dist/index.d.ts CHANGED
@@ -305,6 +305,8 @@ interface TaskPlanning {
305
305
  track?: string;
306
306
  delivery_tracks?: string[];
307
307
  priority_context?: Record<string, TaskPriority>;
308
+ promoted_at?: string;
309
+ promoted_track?: string | null;
308
310
  assignee?: string | null;
309
311
  depends_on?: string[];
310
312
  tags?: string[];
package/dist/index.js CHANGED
@@ -2620,6 +2620,23 @@ function clone_ledger(ledger) {
2620
2620
  function normalize_track2(track) {
2621
2621
  return (track ?? "unassigned").trim().toLowerCase();
2622
2622
  }
2623
+ function promotion_timestamp(task, track) {
2624
+ if (!task.promoted_at) {
2625
+ return 0;
2626
+ }
2627
+ const promotedTrack = task.promoted_track?.trim();
2628
+ const scopedTrack = track?.trim();
2629
+ if (promotedTrack) {
2630
+ if (!scopedTrack) {
2631
+ return 0;
2632
+ }
2633
+ if (normalize_track2(promotedTrack) !== normalize_track2(scopedTrack)) {
2634
+ return 0;
2635
+ }
2636
+ }
2637
+ const parsed = Date.parse(task.promoted_at);
2638
+ return Number.isNaN(parsed) ? 0 : parsed;
2639
+ }
2623
2640
  function task_matches_track(task, track) {
2624
2641
  const normalized = normalize_track2(track);
2625
2642
  if (normalize_track2(task.track) === normalized) {
@@ -2725,6 +2742,11 @@ function schedule_next(graph, options = {}) {
2725
2742
  fits_wip: true
2726
2743
  }));
2727
2744
  scored.sort((a, b) => {
2745
+ const promotionA = promotion_timestamp(a.task, options.track);
2746
+ const promotionB = promotion_timestamp(b.task, options.track);
2747
+ if (promotionA !== promotionB) {
2748
+ return promotionB - promotionA;
2749
+ }
2728
2750
  if (a.score !== b.score) return b.score - a.score;
2729
2751
  return a.task.id.localeCompare(b.task.id);
2730
2752
  });
@@ -3681,6 +3703,10 @@ function isIsoDate(value) {
3681
3703
  }
3682
3704
  return date.toISOString().slice(0, 10) === value;
3683
3705
  }
3706
+ function isIsoDateTime(value) {
3707
+ const date = new Date(value);
3708
+ return !Number.isNaN(date.valueOf());
3709
+ }
3684
3710
  function validateStringArrayField(errors, field, value, options = {}) {
3685
3711
  if (value === void 0) {
3686
3712
  return;
@@ -3807,6 +3833,28 @@ function validateStructural(task, context = {}) {
3807
3833
  validateStringArrayField(errors, "fix_versions", task.fix_versions);
3808
3834
  validateStringArrayField(errors, "released_in", task.released_in);
3809
3835
  validatePriorityContext(errors, task.priority_context);
3836
+ if (task.promoted_at !== void 0) {
3837
+ if (typeof task.promoted_at !== "string" || !isIsoDateTime(task.promoted_at)) {
3838
+ errors.push(
3839
+ error4(
3840
+ "promoted_at",
3841
+ "struct.promoted_at_iso_datetime",
3842
+ "Field 'promoted_at' must be an ISO datetime string."
3843
+ )
3844
+ );
3845
+ }
3846
+ }
3847
+ if (task.promoted_track !== void 0 && task.promoted_track !== null) {
3848
+ if (typeof task.promoted_track !== "string" || task.promoted_track.trim().length === 0) {
3849
+ errors.push(
3850
+ error4(
3851
+ "promoted_track",
3852
+ "struct.promoted_track_string",
3853
+ "Field 'promoted_track' must be a non-empty string or null."
3854
+ )
3855
+ );
3856
+ }
3857
+ }
3810
3858
  if (task.story_points !== void 0 && (!Number.isFinite(task.story_points) || task.story_points < 0)) {
3811
3859
  errors.push(
3812
3860
  error4(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kitsy/coop-core",
3
3
  "description": "Core models, parser, validator, graph, and planning engine for COOP.",
4
- "version": "2.2.3",
4
+ "version": "2.2.5",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "publishConfig": {