@kitsy/coop-core 2.1.2 → 2.2.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/dist/index.cjs CHANGED
@@ -85,6 +85,7 @@ __export(index_exports, {
85
85
  detect_cycle: () => detect_cycle,
86
86
  detect_delivery_risks: () => detect_delivery_risks,
87
87
  determinism_weight: () => determinism_weight,
88
+ effective_priority: () => effective_priority,
88
89
  effective_weekly_hours: () => effective_weekly_hours,
89
90
  effort_or_default: () => effort_or_default,
90
91
  ensureCoopLayout: () => ensureCoopLayout,
@@ -2172,23 +2173,30 @@ var TASK_FIELD_ORDER = [
2172
2173
  "aliases",
2173
2174
  "priority",
2174
2175
  "track",
2176
+ "delivery_tracks",
2177
+ "priority_context",
2175
2178
  "assignee",
2176
2179
  "depends_on",
2177
2180
  "tags",
2178
2181
  "delivery",
2182
+ "fix_versions",
2183
+ "released_in",
2179
2184
  "acceptance",
2180
2185
  "tests_required",
2181
2186
  "origin",
2182
2187
  "complexity",
2183
2188
  "determinism",
2189
+ "story_points",
2184
2190
  "estimate",
2185
2191
  "resources",
2192
+ "time",
2186
2193
  "execution",
2187
2194
  "artifacts",
2188
2195
  "governance",
2189
2196
  "risk",
2190
2197
  "metrics",
2191
- "enterprise"
2198
+ "enterprise",
2199
+ "comments"
2192
2200
  ];
2193
2201
  var ORDERED_TASK_FIELDS = new Set(TASK_FIELD_ORDER);
2194
2202
  function buildOrderedFrontmatter(task, raw) {
@@ -3393,6 +3401,13 @@ function detect_delivery_risks(delivery, graph, velocity, options = {}) {
3393
3401
  }
3394
3402
 
3395
3403
  // src/planning/scorer.ts
3404
+ function effective_priority(task, track) {
3405
+ const candidate = track?.trim();
3406
+ if (candidate && task.priority_context?.[candidate]) {
3407
+ return task.priority_context[candidate];
3408
+ }
3409
+ return task.priority ?? "p2";
3410
+ }
3396
3411
  var DEFAULT_SCORE_WEIGHTS = {
3397
3412
  priority: {
3398
3413
  p0: 100,
@@ -3545,9 +3560,9 @@ function unresolved_dependencies(task, graph) {
3545
3560
  return !dep || dep.status !== "done" && dep.status !== "canceled";
3546
3561
  });
3547
3562
  }
3548
- function priority_weight(task, config) {
3563
+ function priority_weight(task, config, track) {
3549
3564
  const weights = configured_weights(config);
3550
- const priority = task.priority ?? "p2";
3565
+ const priority = effective_priority(task, track);
3551
3566
  return weights.priority[priority];
3552
3567
  }
3553
3568
  function urgency_weight(task, deliveries, today, config) {
@@ -3645,7 +3660,7 @@ function compute_score(task, graph, context = {}) {
3645
3660
  const deliveries = context.deliveries ?? graph.deliveries;
3646
3661
  const today = context.today ?? /* @__PURE__ */ new Date();
3647
3662
  const config = context.config;
3648
- return priority_weight(task, config) + urgency_weight(task, deliveries, today, config) + dependency_unlock_weight(task.id, graph, config) + critical_path_weight(task.id, context.cpm, config) + determinism_weight(task, config) + executor_fit_weight(task, context.target_executor, config) + type_weight(task, config) + complexity_penalty(task, config) + risk_penalty(task, config);
3663
+ return priority_weight(task, config, context.track) + urgency_weight(task, deliveries, today, config) + dependency_unlock_weight(task.id, graph, config) + critical_path_weight(task.id, context.cpm, config) + determinism_weight(task, config) + executor_fit_weight(task, context.target_executor, config) + type_weight(task, config) + complexity_penalty(task, config) + risk_penalty(task, config);
3649
3664
  }
3650
3665
 
3651
3666
  // src/planning/scheduler.ts
@@ -3686,6 +3701,13 @@ function clone_ledger2(ledger) {
3686
3701
  function normalize_track3(track) {
3687
3702
  return (track ?? "unassigned").trim().toLowerCase();
3688
3703
  }
3704
+ function task_matches_track(task, track) {
3705
+ const normalized = normalize_track3(track);
3706
+ if (normalize_track3(task.track) === normalized) {
3707
+ return true;
3708
+ }
3709
+ return (task.delivery_tracks ?? []).some((candidate) => normalize_track3(candidate) === normalized);
3710
+ }
3689
3711
  function find_track_slots2(ledger, track) {
3690
3712
  const normalized = normalize_track3(track);
3691
3713
  return ledger.slots.get(normalized) ?? ledger.slots.get("unassigned") ?? null;
@@ -3753,7 +3775,7 @@ function schedule_next(graph, options = {}) {
3753
3775
  );
3754
3776
  let filtered = ready_tasks;
3755
3777
  if (options.track) {
3756
- filtered = filtered.filter((task) => (task.track ?? "unassigned") === options.track);
3778
+ filtered = filtered.filter((task) => task_matches_track(task, options.track));
3757
3779
  }
3758
3780
  const deliveryFilter = options.delivery;
3759
3781
  if (deliveryFilter) {
@@ -3776,6 +3798,7 @@ function schedule_next(graph, options = {}) {
3776
3798
  cpm,
3777
3799
  today: options.today ?? /* @__PURE__ */ new Date(),
3778
3800
  target_executor: options.executor,
3801
+ track: options.track,
3779
3802
  config: options.config
3780
3803
  }),
3781
3804
  readiness: compute_readiness(task, graph),
@@ -4656,6 +4679,18 @@ function validateSemantic(task, context = {}) {
4656
4679
  }
4657
4680
  }
4658
4681
  }
4682
+ if (task.time?.planned_hours !== void 0 && task.time.logs) {
4683
+ const plannedTotal = task.time.logs.filter((entry) => entry.kind === "planned").reduce((sum, entry) => sum + entry.hours, 0);
4684
+ if (plannedTotal > 0 && plannedTotal !== task.time.planned_hours) {
4685
+ issues.push(
4686
+ warning2(
4687
+ "time.planned_hours",
4688
+ "sem.time_planned_mismatch",
4689
+ `Task planned_hours (${task.time.planned_hours}) does not match total planned logs (${plannedTotal}).`
4690
+ )
4691
+ );
4692
+ }
4693
+ }
4659
4694
  const taskStatuses = asMap(context.taskStatuses);
4660
4695
  if (task.status === "done" && (task.depends_on?.length ?? 0) > 0) {
4661
4696
  const unresolved = (task.depends_on ?? []).filter((depId) => taskStatuses.get(depId) !== "done");
@@ -4744,6 +4779,45 @@ function validateStringArrayField(errors, field, value, options = {}) {
4744
4779
  }
4745
4780
  }
4746
4781
  }
4782
+ function isPriority(value) {
4783
+ return typeof value === "string" && Object.values(TaskPriority).includes(value);
4784
+ }
4785
+ function validatePriorityContext(errors, value) {
4786
+ if (value === void 0) {
4787
+ return;
4788
+ }
4789
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4790
+ errors.push(
4791
+ error4(
4792
+ "priority_context",
4793
+ "struct.priority_context_object",
4794
+ "Field 'priority_context' must be an object keyed by track id."
4795
+ )
4796
+ );
4797
+ return;
4798
+ }
4799
+ for (const [track, priority] of Object.entries(value)) {
4800
+ if (!track.trim()) {
4801
+ errors.push(
4802
+ error4(
4803
+ "priority_context",
4804
+ "struct.priority_context_key",
4805
+ "Field 'priority_context' keys must be non-empty track ids."
4806
+ )
4807
+ );
4808
+ continue;
4809
+ }
4810
+ if (!isPriority(priority)) {
4811
+ errors.push(
4812
+ error4(
4813
+ "priority_context",
4814
+ "struct.priority_context_priority",
4815
+ `Priority override for track '${track}' must be one of ${Object.values(TaskPriority).join(", ")}.`
4816
+ )
4817
+ );
4818
+ }
4819
+ }
4820
+ }
4747
4821
  function validateStructural(task, context = {}) {
4748
4822
  const errors = [];
4749
4823
  const required = ["id", "title", "type", "status", "created", "updated"];
@@ -4810,6 +4884,106 @@ function validateStructural(task, context = {}) {
4810
4884
  }
4811
4885
  validateStringArrayField(errors, "acceptance", task.acceptance);
4812
4886
  validateStringArrayField(errors, "tests_required", task.tests_required);
4887
+ validateStringArrayField(errors, "delivery_tracks", task.delivery_tracks);
4888
+ validateStringArrayField(errors, "fix_versions", task.fix_versions);
4889
+ validateStringArrayField(errors, "released_in", task.released_in);
4890
+ validatePriorityContext(errors, task.priority_context);
4891
+ if (task.story_points !== void 0 && (!Number.isFinite(task.story_points) || task.story_points < 0)) {
4892
+ errors.push(
4893
+ error4(
4894
+ "story_points",
4895
+ "struct.story_points_non_negative",
4896
+ "Field 'story_points' must be a non-negative number."
4897
+ )
4898
+ );
4899
+ }
4900
+ if (task.time !== void 0) {
4901
+ if (!task.time || typeof task.time !== "object" || Array.isArray(task.time)) {
4902
+ errors.push(error4("time", "struct.time_object", "Field 'time' must be an object."));
4903
+ } else {
4904
+ if (task.time.planned_hours !== void 0 && (!Number.isFinite(task.time.planned_hours) || task.time.planned_hours < 0)) {
4905
+ errors.push(
4906
+ error4(
4907
+ "time.planned_hours",
4908
+ "struct.time_planned_hours_non_negative",
4909
+ "Field 'time.planned_hours' must be a non-negative number."
4910
+ )
4911
+ );
4912
+ }
4913
+ if (task.time.logs !== void 0) {
4914
+ if (!Array.isArray(task.time.logs)) {
4915
+ errors.push(error4("time.logs", "struct.time_logs_array", "Field 'time.logs' must be an array."));
4916
+ } else {
4917
+ for (const [index, entry] of task.time.logs.entries()) {
4918
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
4919
+ errors.push(
4920
+ error4("time.logs", "struct.time_logs_object", `Entry ${index} in 'time.logs' must be an object.`)
4921
+ );
4922
+ continue;
4923
+ }
4924
+ const record = entry;
4925
+ if (typeof record.at !== "string" || record.at.trim().length === 0) {
4926
+ errors.push(error4("time.logs", "struct.time_logs_at", `Entry ${index} in 'time.logs' requires 'at'.`));
4927
+ }
4928
+ if (typeof record.actor !== "string" || record.actor.trim().length === 0) {
4929
+ errors.push(
4930
+ error4("time.logs", "struct.time_logs_actor", `Entry ${index} in 'time.logs' requires 'actor'.`)
4931
+ );
4932
+ }
4933
+ if (record.kind !== "planned" && record.kind !== "worked") {
4934
+ errors.push(
4935
+ error4(
4936
+ "time.logs",
4937
+ "struct.time_logs_kind",
4938
+ `Entry ${index} in 'time.logs' must use kind planned|worked.`
4939
+ )
4940
+ );
4941
+ }
4942
+ if (typeof record.hours !== "number" || !Number.isFinite(record.hours) || record.hours < 0) {
4943
+ errors.push(
4944
+ error4(
4945
+ "time.logs",
4946
+ "struct.time_logs_hours",
4947
+ `Entry ${index} in 'time.logs' requires non-negative numeric 'hours'.`
4948
+ )
4949
+ );
4950
+ }
4951
+ if (record.note !== void 0 && typeof record.note !== "string") {
4952
+ errors.push(
4953
+ error4(
4954
+ "time.logs",
4955
+ "struct.time_logs_note",
4956
+ `Entry ${index} in 'time.logs' must use a string note when present.`
4957
+ )
4958
+ );
4959
+ }
4960
+ }
4961
+ }
4962
+ }
4963
+ }
4964
+ }
4965
+ if (task.comments !== void 0) {
4966
+ if (!Array.isArray(task.comments)) {
4967
+ errors.push(error4("comments", "struct.comments_array", "Field 'comments' must be an array."));
4968
+ } else {
4969
+ for (const [index, entry] of task.comments.entries()) {
4970
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
4971
+ errors.push(error4("comments", "struct.comments_object", `Entry ${index} in 'comments' must be an object.`));
4972
+ continue;
4973
+ }
4974
+ const record = entry;
4975
+ if (typeof record.at !== "string" || record.at.trim().length === 0) {
4976
+ errors.push(error4("comments", "struct.comments_at", `Entry ${index} in 'comments' requires 'at'.`));
4977
+ }
4978
+ if (typeof record.author !== "string" || record.author.trim().length === 0) {
4979
+ errors.push(error4("comments", "struct.comments_author", `Entry ${index} in 'comments' requires 'author'.`));
4980
+ }
4981
+ if (typeof record.body !== "string" || record.body.trim().length === 0) {
4982
+ errors.push(error4("comments", "struct.comments_body", `Entry ${index} in 'comments' requires 'body'.`));
4983
+ }
4984
+ }
4985
+ }
4986
+ }
4813
4987
  if (task.origin !== void 0) {
4814
4988
  if (!task.origin || typeof task.origin !== "object" || Array.isArray(task.origin)) {
4815
4989
  errors.push(error4("origin", "struct.origin_object", "Field 'origin' must be an object."));
@@ -5683,6 +5857,7 @@ function validateRepo(rootDir) {
5683
5857
  detect_cycle,
5684
5858
  detect_delivery_risks,
5685
5859
  determinism_weight,
5860
+ effective_priority,
5686
5861
  effective_weekly_hours,
5687
5862
  effort_or_default,
5688
5863
  ensureCoopLayout,
package/dist/index.d.cts CHANGED
@@ -285,14 +285,30 @@ interface TaskCore {
285
285
  created: string;
286
286
  updated: string;
287
287
  }
288
+ interface TaskTimeLog {
289
+ at: string;
290
+ actor: string;
291
+ kind: "planned" | "worked";
292
+ hours: number;
293
+ note?: string;
294
+ }
295
+ interface TaskComment {
296
+ at: string;
297
+ author: string;
298
+ body: string;
299
+ }
288
300
  interface TaskPlanning {
289
301
  aliases?: string[];
290
302
  priority?: TaskPriority;
291
303
  track?: string;
304
+ delivery_tracks?: string[];
305
+ priority_context?: Record<string, TaskPriority>;
292
306
  assignee?: string | null;
293
307
  depends_on?: string[];
294
308
  tags?: string[];
295
309
  delivery?: string | null;
310
+ fix_versions?: string[];
311
+ released_in?: string[];
296
312
  acceptance?: string[];
297
313
  tests_required?: string[];
298
314
  origin?: {
@@ -317,8 +333,13 @@ interface TaskResources {
317
333
  interface TaskEstimation {
318
334
  complexity?: TaskComplexity;
319
335
  determinism?: TaskDeterminism;
336
+ story_points?: number;
320
337
  estimate?: TaskEstimate;
321
338
  resources?: TaskResources;
339
+ time?: {
340
+ planned_hours?: number;
341
+ logs?: TaskTimeLog[];
342
+ };
322
343
  }
323
344
  interface RunbookStep {
324
345
  step: string;
@@ -380,6 +401,7 @@ interface TaskGovernance {
380
401
  target_cycle_time_days?: number;
381
402
  };
382
403
  enterprise?: Record<string, unknown>;
404
+ comments?: TaskComment[];
383
405
  }
384
406
  type Task = TaskCore & TaskPlanning & TaskEstimation & TaskExecution & TaskGovernance;
385
407
  interface TaskComputed {
@@ -1218,15 +1240,17 @@ interface ScoreContext {
1218
1240
  cpm?: CriticalPathResult;
1219
1241
  today?: string | Date;
1220
1242
  target_executor?: ExecutorType;
1243
+ track?: string;
1221
1244
  config?: CoopConfig;
1222
1245
  }
1246
+ declare function effective_priority(task: Task, track?: string): TaskPriority;
1223
1247
  /**
1224
1248
  * Default readiness ranking weights used by `compute_score`.
1225
1249
  * [SPEC: Scheduler v2.0 §4.6]
1226
1250
  */
1227
1251
  declare const DEFAULT_SCORE_WEIGHTS: ScoreWeights;
1228
1252
  /** [SPEC: Scheduler v2.0 §4.6] */
1229
- declare function priority_weight(task: Task, config?: CoopConfig): number;
1253
+ declare function priority_weight(task: Task, config?: CoopConfig, track?: string): number;
1230
1254
  /** [SPEC: Scheduler v2.0 §4.6] */
1231
1255
  declare function urgency_weight(task: Task, deliveries: Map<string, Delivery> | Delivery[], today: string | Date, config?: CoopConfig): number;
1232
1256
  /** [SPEC: Scheduler v2.0 §4.6] */
@@ -1501,4 +1525,4 @@ declare function validateRepo(rootDir: string): {
1501
1525
  warnings: string[];
1502
1526
  };
1503
1527
 
1504
- export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CoopProjectRef, type CoopWorkspaceConfig, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, coop_project_config_path, coop_project_root, coop_projects_dir, coop_workspace_config_path, coop_workspace_dir, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_weekly_hours, effort_or_default, ensureCoopLayout, ensure_workspace_layout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, has_legacy_project_layout, has_v2_projects_layout, is_external_dependency, is_project_initialized, list_projects, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_project_config, read_schema_version, read_workspace_config, renderAgentPrompt, repo_default_project_id, repo_default_project_name, resolve_external_dependencies, resolve_project, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version, write_workspace_config };
1528
+ export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CoopProjectRef, type CoopWorkspaceConfig, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, type TaskComment, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTimeLog, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, coop_project_config_path, coop_project_root, coop_projects_dir, coop_workspace_config_path, coop_workspace_dir, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_priority, effective_weekly_hours, effort_or_default, ensureCoopLayout, ensure_workspace_layout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, has_legacy_project_layout, has_v2_projects_layout, is_external_dependency, is_project_initialized, list_projects, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_project_config, read_schema_version, read_workspace_config, renderAgentPrompt, repo_default_project_id, repo_default_project_name, resolve_external_dependencies, resolve_project, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version, write_workspace_config };
package/dist/index.d.ts CHANGED
@@ -285,14 +285,30 @@ interface TaskCore {
285
285
  created: string;
286
286
  updated: string;
287
287
  }
288
+ interface TaskTimeLog {
289
+ at: string;
290
+ actor: string;
291
+ kind: "planned" | "worked";
292
+ hours: number;
293
+ note?: string;
294
+ }
295
+ interface TaskComment {
296
+ at: string;
297
+ author: string;
298
+ body: string;
299
+ }
288
300
  interface TaskPlanning {
289
301
  aliases?: string[];
290
302
  priority?: TaskPriority;
291
303
  track?: string;
304
+ delivery_tracks?: string[];
305
+ priority_context?: Record<string, TaskPriority>;
292
306
  assignee?: string | null;
293
307
  depends_on?: string[];
294
308
  tags?: string[];
295
309
  delivery?: string | null;
310
+ fix_versions?: string[];
311
+ released_in?: string[];
296
312
  acceptance?: string[];
297
313
  tests_required?: string[];
298
314
  origin?: {
@@ -317,8 +333,13 @@ interface TaskResources {
317
333
  interface TaskEstimation {
318
334
  complexity?: TaskComplexity;
319
335
  determinism?: TaskDeterminism;
336
+ story_points?: number;
320
337
  estimate?: TaskEstimate;
321
338
  resources?: TaskResources;
339
+ time?: {
340
+ planned_hours?: number;
341
+ logs?: TaskTimeLog[];
342
+ };
322
343
  }
323
344
  interface RunbookStep {
324
345
  step: string;
@@ -380,6 +401,7 @@ interface TaskGovernance {
380
401
  target_cycle_time_days?: number;
381
402
  };
382
403
  enterprise?: Record<string, unknown>;
404
+ comments?: TaskComment[];
383
405
  }
384
406
  type Task = TaskCore & TaskPlanning & TaskEstimation & TaskExecution & TaskGovernance;
385
407
  interface TaskComputed {
@@ -1218,15 +1240,17 @@ interface ScoreContext {
1218
1240
  cpm?: CriticalPathResult;
1219
1241
  today?: string | Date;
1220
1242
  target_executor?: ExecutorType;
1243
+ track?: string;
1221
1244
  config?: CoopConfig;
1222
1245
  }
1246
+ declare function effective_priority(task: Task, track?: string): TaskPriority;
1223
1247
  /**
1224
1248
  * Default readiness ranking weights used by `compute_score`.
1225
1249
  * [SPEC: Scheduler v2.0 §4.6]
1226
1250
  */
1227
1251
  declare const DEFAULT_SCORE_WEIGHTS: ScoreWeights;
1228
1252
  /** [SPEC: Scheduler v2.0 §4.6] */
1229
- declare function priority_weight(task: Task, config?: CoopConfig): number;
1253
+ declare function priority_weight(task: Task, config?: CoopConfig, track?: string): number;
1230
1254
  /** [SPEC: Scheduler v2.0 §4.6] */
1231
1255
  declare function urgency_weight(task: Task, deliveries: Map<string, Delivery> | Delivery[], today: string | Date, config?: CoopConfig): number;
1232
1256
  /** [SPEC: Scheduler v2.0 §4.6] */
@@ -1501,4 +1525,4 @@ declare function validateRepo(rootDir: string): {
1501
1525
  warnings: string[];
1502
1526
  };
1503
1527
 
1504
- export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CoopProjectRef, type CoopWorkspaceConfig, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, coop_project_config_path, coop_project_root, coop_projects_dir, coop_workspace_config_path, coop_workspace_dir, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_weekly_hours, effort_or_default, ensureCoopLayout, ensure_workspace_layout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, has_legacy_project_layout, has_v2_projects_layout, is_external_dependency, is_project_initialized, list_projects, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_project_config, read_schema_version, read_workspace_config, renderAgentPrompt, repo_default_project_id, repo_default_project_name, resolve_external_dependencies, resolve_project, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version, write_workspace_config };
1528
+ export { type AIAgent, type AIResource, type AgentSpec, type AllocationResult, ArtifactType, type AuthConfig, type AuthPolicy, type AutoTransitionResult, type AvailabilityWindow, type BacklogItem, type BacklogItemData, COOP_EVENT_TYPES, CURRENT_SCHEMA_VERSION, type CapacityLedger, type ComparisonResult, type ComparisonRow, type ComputeNode, type ComputeResource, type CoopConfig, type CoopEvent, CoopEventEmitter, type CoopEventType, type CoopProjectRef, type CoopWorkspaceConfig, type CreateItemParams, type CriticalPathResult, type CriticalPathTaskMetrics, DEFAULT_SCORE_WEIGHTS, type Delivery, type DeliveryAtRisk, type DeliveryBudget, type DeliveryCommitted, type DeliveryGovernance, type DeliveryRisk, type DeliveryRiskType, type DeliveryScope, DeliveryStatus, type DetectedDeliveryRisk, type EffectiveCapacity, type EventByType, type ExecutionConstraints, type ExecutionPermissions, ExecutorType, type ExternalDependencyRef, type ExternalDependencyResolution, type ExternalDependencyResolutionStatus, type ExternalDependencyResolverOptions, type ExternalRepoConfig, type FeasibilityResult, type FeasibilityRisk, type FeasibilityStatus, type FeasibilitySummary, type FilterSpec, type FrontmatterParseResult, type GraphCycleDetected, type GraphValidationContext, type GraphValidationResult, type HookRunResult, type HumanMember, type HumanResource, ITEM_STATUSES, ITEM_TYPES, type Idea, IdeaStatus, IndexManager, type IndexStatus, type ItemLinks, type ItemStatus, type ItemType, MIGRATIONS, type MigrateRepositoryOptions, type MigrationContext, type MigrationDefinition, type MigrationReport, type MonteCarloHistogramBucket, type MonteCarloOptions, type MonteCarloResult, type MonteCarloWorkerPayload, type ParsedDelivery, type ParsedIdea, type ParsedTask, type Permission, type PermissionContext, type PluginAction, type PluginActionConsole, type PluginActionGitHubPr, type PluginActionHandler, type PluginActionHandlerResult, type PluginActionWebhook, type PluginManifest, type PluginRunOptions, type PluginRunRecord, type PluginTrigger, type PolicyAction, type ReadinessComputation, type ReadinessPartitions, type ReadinessState, type ReadinessTransitionEvent, type ReadinessWarning, type ReferentialValidationContext, type RepoConfig, type RepoState, type ResourceProfile, RiskLevel, type Role, type Run, type RunCompleted, type RunFailed, type RunResourcesConsumed, type RunStarted, RunStatus, type RunStepResult, RunStepStatus, RunbookAction, type RunbookStep, type ScheduleOptions, type ScoreContext, type ScoredTask, type SemanticValidationContext, type SimulationResult, type StructuralValidationContext, type Task, type TaskAssigned, type TaskComment, TaskComplexity, type TaskComputed, type TaskCore, type TaskCreated, TaskDeterminism, type TaskEstimate, type TaskEstimation, type TaskExecution, type TaskGovernance, type TaskGraph, type TaskPlanning, TaskPriority, type TaskResources, type TaskScheduleEntry, TaskStatus, type TaskTimeLog, type TaskTransitioned, type TaskTransitionedEvent, TaskType, type Track, type TrackUtilization, type TrackWip, type TransitionContext, type TransitionResult, type TransitionValidationContext, type UpdateItemParams, VALID_TASK_TRANSITIONS, VALID_TRANSITIONS, type ValidationContext, type ValidationError, type ValidationLevel, type ValidationResult, type VelocityMetrics, type VelocityPoint, type VelocityTrend, type WhatIfBaseline, type WhatIfModification, type WriteTaskOptions, allocate, allocate_ai, allocate_ai_tokens, analyze_feasibility, analyze_what_if, build_capacity_ledger, build_graph, check_blocked, check_permission, check_unblocked, check_wip, completeItem, complexity_penalty, compute_all_readiness, compute_critical_path, compute_readiness, compute_readiness_with_corrections, compute_score, compute_velocity, coop_project_config_path, coop_project_root, coop_projects_dir, coop_workspace_config_path, coop_workspace_dir, createItem, create_seeded_rng, critical_path_weight, deleteItem, dependency_unlock_weight, detect_cycle, detect_delivery_risks, determinism_weight, effective_priority, effective_weekly_hours, effort_or_default, ensureCoopLayout, ensure_workspace_layout, executor_fit_weight, external_dependencies_for_task, extract_subgraph, findRepoRoot, find_external_dependencies, getItemById, get_remaining_tokens, get_user_role, has_legacy_project_layout, has_v2_projects_layout, is_external_dependency, is_project_initialized, list_projects, loadState, load_auth_config, load_completed_runs, load_graph, load_plugins, migrate_repository, migrate_task, monte_carlo_forecast, parseDeliveryContent, parseDeliveryFile, parseFrontmatterContent, parseFrontmatterFile, parseIdeaContent, parseIdeaFile, parseTaskContent, parseTaskFile, parseYamlContent, parseYamlFile, parse_external_dependency, partition_by_readiness, pert_hours, pert_stddev, priority_weight, queryItems, read_project_config, read_schema_version, read_workspace_config, renderAgentPrompt, repo_default_project_id, repo_default_project_name, resolve_external_dependencies, resolve_project, risk_penalty, run_hook, run_monte_carlo_chunk, run_plugins_for_event, sample_pert_beta, sample_task_hours, schedule_next, simulate_schedule, stringifyFrontmatter, stringifyYamlContent, task_effort_hours, topological_sort, transition, transitive_dependencies, transitive_dependents, type_weight, updateItem, urgency_weight, validate, validateReferential, validateRepo, validateSemantic, validateStructural, validateTransition, validate_graph, validate_transition, writeTask, writeYamlFile, write_schema_version, write_workspace_config };
package/dist/index.js CHANGED
@@ -1962,23 +1962,30 @@ var TASK_FIELD_ORDER = [
1962
1962
  "aliases",
1963
1963
  "priority",
1964
1964
  "track",
1965
+ "delivery_tracks",
1966
+ "priority_context",
1965
1967
  "assignee",
1966
1968
  "depends_on",
1967
1969
  "tags",
1968
1970
  "delivery",
1971
+ "fix_versions",
1972
+ "released_in",
1969
1973
  "acceptance",
1970
1974
  "tests_required",
1971
1975
  "origin",
1972
1976
  "complexity",
1973
1977
  "determinism",
1978
+ "story_points",
1974
1979
  "estimate",
1975
1980
  "resources",
1981
+ "time",
1976
1982
  "execution",
1977
1983
  "artifacts",
1978
1984
  "governance",
1979
1985
  "risk",
1980
1986
  "metrics",
1981
- "enterprise"
1987
+ "enterprise",
1988
+ "comments"
1982
1989
  ];
1983
1990
  var ORDERED_TASK_FIELDS = new Set(TASK_FIELD_ORDER);
1984
1991
  function buildOrderedFrontmatter(task, raw) {
@@ -2310,6 +2317,13 @@ function detect_delivery_risks(delivery, graph, velocity, options = {}) {
2310
2317
  }
2311
2318
 
2312
2319
  // src/planning/scorer.ts
2320
+ function effective_priority(task, track) {
2321
+ const candidate = track?.trim();
2322
+ if (candidate && task.priority_context?.[candidate]) {
2323
+ return task.priority_context[candidate];
2324
+ }
2325
+ return task.priority ?? "p2";
2326
+ }
2313
2327
  var DEFAULT_SCORE_WEIGHTS = {
2314
2328
  priority: {
2315
2329
  p0: 100,
@@ -2462,9 +2476,9 @@ function unresolved_dependencies(task, graph) {
2462
2476
  return !dep || dep.status !== "done" && dep.status !== "canceled";
2463
2477
  });
2464
2478
  }
2465
- function priority_weight(task, config) {
2479
+ function priority_weight(task, config, track) {
2466
2480
  const weights = configured_weights(config);
2467
- const priority = task.priority ?? "p2";
2481
+ const priority = effective_priority(task, track);
2468
2482
  return weights.priority[priority];
2469
2483
  }
2470
2484
  function urgency_weight(task, deliveries, today, config) {
@@ -2562,7 +2576,7 @@ function compute_score(task, graph, context = {}) {
2562
2576
  const deliveries = context.deliveries ?? graph.deliveries;
2563
2577
  const today = context.today ?? /* @__PURE__ */ new Date();
2564
2578
  const config = context.config;
2565
- return priority_weight(task, config) + urgency_weight(task, deliveries, today, config) + dependency_unlock_weight(task.id, graph, config) + critical_path_weight(task.id, context.cpm, config) + determinism_weight(task, config) + executor_fit_weight(task, context.target_executor, config) + type_weight(task, config) + complexity_penalty(task, config) + risk_penalty(task, config);
2579
+ return priority_weight(task, config, context.track) + urgency_weight(task, deliveries, today, config) + dependency_unlock_weight(task.id, graph, config) + critical_path_weight(task.id, context.cpm, config) + determinism_weight(task, config) + executor_fit_weight(task, context.target_executor, config) + type_weight(task, config) + complexity_penalty(task, config) + risk_penalty(task, config);
2566
2580
  }
2567
2581
 
2568
2582
  // src/planning/scheduler.ts
@@ -2603,6 +2617,13 @@ function clone_ledger(ledger) {
2603
2617
  function normalize_track2(track) {
2604
2618
  return (track ?? "unassigned").trim().toLowerCase();
2605
2619
  }
2620
+ function task_matches_track(task, track) {
2621
+ const normalized = normalize_track2(track);
2622
+ if (normalize_track2(task.track) === normalized) {
2623
+ return true;
2624
+ }
2625
+ return (task.delivery_tracks ?? []).some((candidate) => normalize_track2(candidate) === normalized);
2626
+ }
2606
2627
  function find_track_slots(ledger, track) {
2607
2628
  const normalized = normalize_track2(track);
2608
2629
  return ledger.slots.get(normalized) ?? ledger.slots.get("unassigned") ?? null;
@@ -2670,7 +2691,7 @@ function schedule_next(graph, options = {}) {
2670
2691
  );
2671
2692
  let filtered = ready_tasks;
2672
2693
  if (options.track) {
2673
- filtered = filtered.filter((task) => (task.track ?? "unassigned") === options.track);
2694
+ filtered = filtered.filter((task) => task_matches_track(task, options.track));
2674
2695
  }
2675
2696
  const deliveryFilter = options.delivery;
2676
2697
  if (deliveryFilter) {
@@ -2693,6 +2714,7 @@ function schedule_next(graph, options = {}) {
2693
2714
  cpm,
2694
2715
  today: options.today ?? /* @__PURE__ */ new Date(),
2695
2716
  target_executor: options.executor,
2717
+ track: options.track,
2696
2718
  config: options.config
2697
2719
  }),
2698
2720
  readiness: compute_readiness(task, graph),
@@ -3573,6 +3595,18 @@ function validateSemantic(task, context = {}) {
3573
3595
  }
3574
3596
  }
3575
3597
  }
3598
+ if (task.time?.planned_hours !== void 0 && task.time.logs) {
3599
+ const plannedTotal = task.time.logs.filter((entry) => entry.kind === "planned").reduce((sum, entry) => sum + entry.hours, 0);
3600
+ if (plannedTotal > 0 && plannedTotal !== task.time.planned_hours) {
3601
+ issues.push(
3602
+ warning2(
3603
+ "time.planned_hours",
3604
+ "sem.time_planned_mismatch",
3605
+ `Task planned_hours (${task.time.planned_hours}) does not match total planned logs (${plannedTotal}).`
3606
+ )
3607
+ );
3608
+ }
3609
+ }
3576
3610
  const taskStatuses = asMap(context.taskStatuses);
3577
3611
  if (task.status === "done" && (task.depends_on?.length ?? 0) > 0) {
3578
3612
  const unresolved = (task.depends_on ?? []).filter((depId) => taskStatuses.get(depId) !== "done");
@@ -3661,6 +3695,45 @@ function validateStringArrayField(errors, field, value, options = {}) {
3661
3695
  }
3662
3696
  }
3663
3697
  }
3698
+ function isPriority(value) {
3699
+ return typeof value === "string" && Object.values(TaskPriority).includes(value);
3700
+ }
3701
+ function validatePriorityContext(errors, value) {
3702
+ if (value === void 0) {
3703
+ return;
3704
+ }
3705
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3706
+ errors.push(
3707
+ error4(
3708
+ "priority_context",
3709
+ "struct.priority_context_object",
3710
+ "Field 'priority_context' must be an object keyed by track id."
3711
+ )
3712
+ );
3713
+ return;
3714
+ }
3715
+ for (const [track, priority] of Object.entries(value)) {
3716
+ if (!track.trim()) {
3717
+ errors.push(
3718
+ error4(
3719
+ "priority_context",
3720
+ "struct.priority_context_key",
3721
+ "Field 'priority_context' keys must be non-empty track ids."
3722
+ )
3723
+ );
3724
+ continue;
3725
+ }
3726
+ if (!isPriority(priority)) {
3727
+ errors.push(
3728
+ error4(
3729
+ "priority_context",
3730
+ "struct.priority_context_priority",
3731
+ `Priority override for track '${track}' must be one of ${Object.values(TaskPriority).join(", ")}.`
3732
+ )
3733
+ );
3734
+ }
3735
+ }
3736
+ }
3664
3737
  function validateStructural(task, context = {}) {
3665
3738
  const errors = [];
3666
3739
  const required = ["id", "title", "type", "status", "created", "updated"];
@@ -3727,6 +3800,106 @@ function validateStructural(task, context = {}) {
3727
3800
  }
3728
3801
  validateStringArrayField(errors, "acceptance", task.acceptance);
3729
3802
  validateStringArrayField(errors, "tests_required", task.tests_required);
3803
+ validateStringArrayField(errors, "delivery_tracks", task.delivery_tracks);
3804
+ validateStringArrayField(errors, "fix_versions", task.fix_versions);
3805
+ validateStringArrayField(errors, "released_in", task.released_in);
3806
+ validatePriorityContext(errors, task.priority_context);
3807
+ if (task.story_points !== void 0 && (!Number.isFinite(task.story_points) || task.story_points < 0)) {
3808
+ errors.push(
3809
+ error4(
3810
+ "story_points",
3811
+ "struct.story_points_non_negative",
3812
+ "Field 'story_points' must be a non-negative number."
3813
+ )
3814
+ );
3815
+ }
3816
+ if (task.time !== void 0) {
3817
+ if (!task.time || typeof task.time !== "object" || Array.isArray(task.time)) {
3818
+ errors.push(error4("time", "struct.time_object", "Field 'time' must be an object."));
3819
+ } else {
3820
+ if (task.time.planned_hours !== void 0 && (!Number.isFinite(task.time.planned_hours) || task.time.planned_hours < 0)) {
3821
+ errors.push(
3822
+ error4(
3823
+ "time.planned_hours",
3824
+ "struct.time_planned_hours_non_negative",
3825
+ "Field 'time.planned_hours' must be a non-negative number."
3826
+ )
3827
+ );
3828
+ }
3829
+ if (task.time.logs !== void 0) {
3830
+ if (!Array.isArray(task.time.logs)) {
3831
+ errors.push(error4("time.logs", "struct.time_logs_array", "Field 'time.logs' must be an array."));
3832
+ } else {
3833
+ for (const [index, entry] of task.time.logs.entries()) {
3834
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3835
+ errors.push(
3836
+ error4("time.logs", "struct.time_logs_object", `Entry ${index} in 'time.logs' must be an object.`)
3837
+ );
3838
+ continue;
3839
+ }
3840
+ const record = entry;
3841
+ if (typeof record.at !== "string" || record.at.trim().length === 0) {
3842
+ errors.push(error4("time.logs", "struct.time_logs_at", `Entry ${index} in 'time.logs' requires 'at'.`));
3843
+ }
3844
+ if (typeof record.actor !== "string" || record.actor.trim().length === 0) {
3845
+ errors.push(
3846
+ error4("time.logs", "struct.time_logs_actor", `Entry ${index} in 'time.logs' requires 'actor'.`)
3847
+ );
3848
+ }
3849
+ if (record.kind !== "planned" && record.kind !== "worked") {
3850
+ errors.push(
3851
+ error4(
3852
+ "time.logs",
3853
+ "struct.time_logs_kind",
3854
+ `Entry ${index} in 'time.logs' must use kind planned|worked.`
3855
+ )
3856
+ );
3857
+ }
3858
+ if (typeof record.hours !== "number" || !Number.isFinite(record.hours) || record.hours < 0) {
3859
+ errors.push(
3860
+ error4(
3861
+ "time.logs",
3862
+ "struct.time_logs_hours",
3863
+ `Entry ${index} in 'time.logs' requires non-negative numeric 'hours'.`
3864
+ )
3865
+ );
3866
+ }
3867
+ if (record.note !== void 0 && typeof record.note !== "string") {
3868
+ errors.push(
3869
+ error4(
3870
+ "time.logs",
3871
+ "struct.time_logs_note",
3872
+ `Entry ${index} in 'time.logs' must use a string note when present.`
3873
+ )
3874
+ );
3875
+ }
3876
+ }
3877
+ }
3878
+ }
3879
+ }
3880
+ }
3881
+ if (task.comments !== void 0) {
3882
+ if (!Array.isArray(task.comments)) {
3883
+ errors.push(error4("comments", "struct.comments_array", "Field 'comments' must be an array."));
3884
+ } else {
3885
+ for (const [index, entry] of task.comments.entries()) {
3886
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
3887
+ errors.push(error4("comments", "struct.comments_object", `Entry ${index} in 'comments' must be an object.`));
3888
+ continue;
3889
+ }
3890
+ const record = entry;
3891
+ if (typeof record.at !== "string" || record.at.trim().length === 0) {
3892
+ errors.push(error4("comments", "struct.comments_at", `Entry ${index} in 'comments' requires 'at'.`));
3893
+ }
3894
+ if (typeof record.author !== "string" || record.author.trim().length === 0) {
3895
+ errors.push(error4("comments", "struct.comments_author", `Entry ${index} in 'comments' requires 'author'.`));
3896
+ }
3897
+ if (typeof record.body !== "string" || record.body.trim().length === 0) {
3898
+ errors.push(error4("comments", "struct.comments_body", `Entry ${index} in 'comments' requires 'body'.`));
3899
+ }
3900
+ }
3901
+ }
3902
+ }
3730
3903
  if (task.origin !== void 0) {
3731
3904
  if (!task.origin || typeof task.origin !== "object" || Array.isArray(task.origin)) {
3732
3905
  errors.push(error4("origin", "struct.origin_object", "Field 'origin' must be an object."));
@@ -4599,6 +4772,7 @@ export {
4599
4772
  detect_cycle,
4600
4773
  detect_delivery_risks,
4601
4774
  determinism_weight,
4775
+ effective_priority,
4602
4776
  effective_weekly_hours,
4603
4777
  effort_or_default,
4604
4778
  ensureCoopLayout,
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.1.2",
4
+ "version": "2.2.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "publishConfig": {