@hasna/todos 0.11.65 → 0.11.66

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.
@@ -4774,38 +4774,7 @@ var init_task_lists = __esm(() => {
4774
4774
  init_projects();
4775
4775
  });
4776
4776
 
4777
- // src/lib/shared-events.ts
4778
- function taskEventData(task, extra = {}) {
4779
- return {
4780
- id: task.id,
4781
- task_id: task.id,
4782
- short_id: task.short_id,
4783
- title: task.title,
4784
- description: task.description,
4785
- status: task.status,
4786
- priority: task.priority,
4787
- project_id: task.project_id,
4788
- parent_id: task.parent_id,
4789
- plan_id: task.plan_id,
4790
- task_list_id: task.task_list_id,
4791
- agent_id: task.agent_id,
4792
- assigned_to: task.assigned_to,
4793
- session_id: task.session_id,
4794
- working_dir: task.working_dir,
4795
- tags: task.tags,
4796
- metadata: task.metadata,
4797
- version: task.version,
4798
- created_at: task.created_at,
4799
- updated_at: task.updated_at,
4800
- started_at: task.started_at,
4801
- completed_at: task.completed_at,
4802
- due_at: task.due_at,
4803
- requires_approval: task.requires_approval,
4804
- approved_by: task.approved_by,
4805
- approved_at: task.approved_at,
4806
- ...extra
4807
- };
4808
- }
4777
+ // src/lib/task-route-contract.ts
4809
4778
  function booleanField(value) {
4810
4779
  if (typeof value === "boolean")
4811
4780
  return value;
@@ -4827,21 +4796,31 @@ function booleanField(value) {
4827
4796
  function objectField(value) {
4828
4797
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
4829
4798
  }
4830
- function firstBoolean(records, keys) {
4799
+ function collectBooleans(records, keys) {
4800
+ const values = [];
4831
4801
  for (const record of records) {
4802
+ if (!record)
4803
+ continue;
4832
4804
  for (const key of keys) {
4833
4805
  const value = booleanField(record[key]);
4834
4806
  if (value !== undefined)
4835
- return value;
4807
+ values.push(value);
4836
4808
  }
4837
4809
  }
4838
- return;
4810
+ return values;
4839
4811
  }
4840
- function routingAutomationMetadata(task) {
4841
- const automation = objectField(task.metadata.automation);
4842
- const records = [task.metadata];
4843
- if (automation)
4844
- records.push(automation);
4812
+ function mergedBoolean(records, keys, trueWins) {
4813
+ const values = collectBooleans(records, keys);
4814
+ if (values.length === 0)
4815
+ return;
4816
+ if (trueWins)
4817
+ return values.some(Boolean);
4818
+ return values.includes(false) ? false : true;
4819
+ }
4820
+ function routingAutomationMetadata(task, taskList) {
4821
+ const taskAutomation = objectField(task.metadata.automation);
4822
+ const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
4823
+ const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
4845
4824
  const result = {};
4846
4825
  const aliases = [
4847
4826
  ["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
@@ -4852,7 +4831,7 @@ function routingAutomationMetadata(task) {
4852
4831
  ["approval_required", ["approval_required", "approvalRequired"]]
4853
4832
  ];
4854
4833
  for (const [canonical, keys] of aliases) {
4855
- const value = firstBoolean(records, keys);
4834
+ const value = mergedBoolean(records, keys, canonical !== "allowed");
4856
4835
  if (value !== undefined)
4857
4836
  result[canonical] = value;
4858
4837
  }
@@ -4860,23 +4839,88 @@ function routingAutomationMetadata(task) {
4860
4839
  result.requires_approval = true;
4861
4840
  return Object.keys(result).length > 0 ? result : undefined;
4862
4841
  }
4842
+ function routeEnabledForTask(task, taskList) {
4843
+ const explicit = booleanField(task.metadata.route_enabled);
4844
+ if (explicit !== undefined)
4845
+ return explicit;
4846
+ if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
4847
+ return true;
4848
+ const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
4849
+ if (taskListDefault !== undefined)
4850
+ return taskListDefault;
4851
+ return;
4852
+ }
4853
+ function stringField(value) {
4854
+ return typeof value === "string" && value.trim() ? value : undefined;
4855
+ }
4856
+ function workflowPointersFromMetadata(metadata) {
4857
+ const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
4858
+ return {
4859
+ current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
4860
+ current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
4861
+ latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
4862
+ latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
4863
+ workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
4864
+ };
4865
+ }
4866
+ function classifyProjectKind(path) {
4867
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
4868
+ }
4869
+ function isWorktreePath(path) {
4870
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
4871
+ }
4872
+ function inferRootProjectId(project) {
4873
+ return isWorktreePath(project.path) ? null : project.id;
4874
+ }
4875
+ var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1";
4876
+
4877
+ // src/lib/shared-events.ts
4878
+ function taskEventData(task, extra = {}) {
4879
+ return {
4880
+ id: task.id,
4881
+ task_id: task.id,
4882
+ short_id: task.short_id,
4883
+ title: task.title,
4884
+ description: task.description,
4885
+ status: task.status,
4886
+ priority: task.priority,
4887
+ project_id: task.project_id,
4888
+ parent_id: task.parent_id,
4889
+ plan_id: task.plan_id,
4890
+ task_list_id: task.task_list_id,
4891
+ agent_id: task.agent_id,
4892
+ assigned_to: task.assigned_to,
4893
+ session_id: task.session_id,
4894
+ working_dir: task.working_dir,
4895
+ tags: task.tags,
4896
+ metadata: task.metadata,
4897
+ version: task.version,
4898
+ created_at: task.created_at,
4899
+ updated_at: task.updated_at,
4900
+ started_at: task.started_at,
4901
+ completed_at: task.completed_at,
4902
+ due_at: task.due_at,
4903
+ requires_approval: task.requires_approval,
4904
+ approved_by: task.approved_by,
4905
+ approved_at: task.approved_at,
4906
+ ...extra
4907
+ };
4908
+ }
4863
4909
  function taskEventMetadata(task) {
4864
4910
  const metadata = {
4865
4911
  package: "@hasna/todos",
4866
4912
  todos_event_schema_version: 1,
4913
+ route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
4867
4914
  task_id: task.id,
4868
4915
  task_short_id: task.short_id,
4869
4916
  project_id: task.project_id,
4870
4917
  task_list_id: task.task_list_id,
4871
4918
  working_dir: task.working_dir
4872
4919
  };
4873
- const routeEnabled = booleanField(task.metadata.route_enabled);
4874
- if (routeEnabled !== undefined) {
4875
- metadata.route_enabled = routeEnabled;
4876
- }
4877
- const automation = routingAutomationMetadata(task);
4878
- if (automation) {
4879
- metadata.automation = automation;
4920
+ const pointers = workflowPointersFromMetadata(task.metadata);
4921
+ for (const [key, value] of Object.entries(pointers)) {
4922
+ if (value)
4923
+ metadata[key] = value;
4880
4924
  }
4881
4925
  try {
4882
4926
  const project = task.project_id ? getProject(task.project_id) : null;
@@ -4905,18 +4949,20 @@ function taskEventMetadata(task) {
4905
4949
  metadata.task_list_project_id = taskList.project_id;
4906
4950
  metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
4907
4951
  }
4952
+ const routeEnabled = routeEnabledForTask(task, taskList);
4953
+ if (routeEnabled !== undefined) {
4954
+ metadata.route_enabled = routeEnabled;
4955
+ }
4956
+ const automation = routingAutomationMetadata(task, taskList);
4957
+ if (automation) {
4958
+ metadata.automation = automation;
4959
+ metadata.route_blocked_by_no_auto = automation.no_auto === true;
4960
+ metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
4961
+ metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
4962
+ }
4908
4963
  } catch {}
4909
4964
  return metadata;
4910
4965
  }
4911
- function classifyProjectKind(path) {
4912
- return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
4913
- }
4914
- function isWorktreePath(path) {
4915
- return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
4916
- }
4917
- function inferRootProjectId(project) {
4918
- return isWorktreePath(project.path) ? null : project.id;
4919
- }
4920
4966
  function readMachineLocalPath(project) {
4921
4967
  const machineId = process.env["TODOS_MACHINE_ID"];
4922
4968
  if (!machineId)
@@ -33684,7 +33730,19 @@ function bootstrapProject(options = {}, db) {
33684
33730
  }
33685
33731
  setMachineLocalPath(project.id, discovery.projectPath, d);
33686
33732
  const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
33687
- const taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
33733
+ let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
33734
+ if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
33735
+ taskList = updateTaskList(taskList.id, {
33736
+ metadata: {
33737
+ ...taskList.metadata,
33738
+ route_enabled: true,
33739
+ automation: {
33740
+ ...taskList.metadata.automation && typeof taskList.metadata.automation === "object" && !Array.isArray(taskList.metadata.automation) ? taskList.metadata.automation : {},
33741
+ no_auto: false
33742
+ }
33743
+ }
33744
+ }, d);
33745
+ }
33688
33746
  const createdSources = [];
33689
33747
  for (const source of [
33690
33748
  addSourceOnce(project.id, "local", "Project root", discovery.projectPath, { role: "project-root" }, d),
package/dist/storage.js CHANGED
@@ -5176,39 +5176,9 @@ function ensureTaskList(name, slug, projectId, db) {
5176
5176
  return createTaskList({ name, slug, project_id: projectId }, d);
5177
5177
  }
5178
5178
 
5179
- // src/lib/shared-events.ts
5180
- var SOURCE = "todos";
5181
- function taskEventData(task, extra = {}) {
5182
- return {
5183
- id: task.id,
5184
- task_id: task.id,
5185
- short_id: task.short_id,
5186
- title: task.title,
5187
- description: task.description,
5188
- status: task.status,
5189
- priority: task.priority,
5190
- project_id: task.project_id,
5191
- parent_id: task.parent_id,
5192
- plan_id: task.plan_id,
5193
- task_list_id: task.task_list_id,
5194
- agent_id: task.agent_id,
5195
- assigned_to: task.assigned_to,
5196
- session_id: task.session_id,
5197
- working_dir: task.working_dir,
5198
- tags: task.tags,
5199
- metadata: task.metadata,
5200
- version: task.version,
5201
- created_at: task.created_at,
5202
- updated_at: task.updated_at,
5203
- started_at: task.started_at,
5204
- completed_at: task.completed_at,
5205
- due_at: task.due_at,
5206
- requires_approval: task.requires_approval,
5207
- approved_by: task.approved_by,
5208
- approved_at: task.approved_at,
5209
- ...extra
5210
- };
5211
- }
5179
+ // src/lib/task-route-contract.ts
5180
+ var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1";
5181
+ var TASK_WORKFLOW_POINTER_SCHEMA_VERSION = "todos.task_workflow_pointer.v1";
5212
5182
  function booleanField(value) {
5213
5183
  if (typeof value === "boolean")
5214
5184
  return value;
@@ -5230,21 +5200,31 @@ function booleanField(value) {
5230
5200
  function objectField(value) {
5231
5201
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
5232
5202
  }
5233
- function firstBoolean(records, keys) {
5203
+ function collectBooleans(records, keys) {
5204
+ const values = [];
5234
5205
  for (const record of records) {
5206
+ if (!record)
5207
+ continue;
5235
5208
  for (const key of keys) {
5236
5209
  const value = booleanField(record[key]);
5237
5210
  if (value !== undefined)
5238
- return value;
5211
+ values.push(value);
5239
5212
  }
5240
5213
  }
5241
- return;
5214
+ return values;
5242
5215
  }
5243
- function routingAutomationMetadata(task) {
5244
- const automation = objectField(task.metadata.automation);
5245
- const records = [task.metadata];
5246
- if (automation)
5247
- records.push(automation);
5216
+ function mergedBoolean(records, keys, trueWins) {
5217
+ const values = collectBooleans(records, keys);
5218
+ if (values.length === 0)
5219
+ return;
5220
+ if (trueWins)
5221
+ return values.some(Boolean);
5222
+ return values.includes(false) ? false : true;
5223
+ }
5224
+ function routingAutomationMetadata(task, taskList) {
5225
+ const taskAutomation = objectField(task.metadata.automation);
5226
+ const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
5227
+ const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
5248
5228
  const result = {};
5249
5229
  const aliases = [
5250
5230
  ["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
@@ -5255,7 +5235,7 @@ function routingAutomationMetadata(task) {
5255
5235
  ["approval_required", ["approval_required", "approvalRequired"]]
5256
5236
  ];
5257
5237
  for (const [canonical, keys] of aliases) {
5258
- const value = firstBoolean(records, keys);
5238
+ const value = mergedBoolean(records, keys, canonical !== "allowed");
5259
5239
  if (value !== undefined)
5260
5240
  result[canonical] = value;
5261
5241
  }
@@ -5263,23 +5243,91 @@ function routingAutomationMetadata(task) {
5263
5243
  result.requires_approval = true;
5264
5244
  return Object.keys(result).length > 0 ? result : undefined;
5265
5245
  }
5246
+ function routeEnabledForTask(task, taskList) {
5247
+ const explicit = booleanField(task.metadata.route_enabled);
5248
+ if (explicit !== undefined)
5249
+ return explicit;
5250
+ if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
5251
+ return true;
5252
+ const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
5253
+ if (taskListDefault !== undefined)
5254
+ return taskListDefault;
5255
+ return;
5256
+ }
5257
+ function stringField(value) {
5258
+ return typeof value === "string" && value.trim() ? value : undefined;
5259
+ }
5260
+ function workflowPointersFromMetadata(metadata) {
5261
+ const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
5262
+ return {
5263
+ current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
5264
+ current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
5265
+ latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
5266
+ latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
5267
+ workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
5268
+ };
5269
+ }
5270
+ function compactWorkflowPointers(pointers) {
5271
+ return Object.fromEntries(Object.entries(pointers).filter(([, value]) => typeof value === "string" && value.length > 0));
5272
+ }
5273
+ function classifyProjectKind(path) {
5274
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
5275
+ }
5276
+ function isWorktreePath(path) {
5277
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
5278
+ }
5279
+ function inferRootProjectId(project) {
5280
+ return isWorktreePath(project.path) ? null : project.id;
5281
+ }
5282
+
5283
+ // src/lib/shared-events.ts
5284
+ var SOURCE = "todos";
5285
+ function taskEventData(task, extra = {}) {
5286
+ return {
5287
+ id: task.id,
5288
+ task_id: task.id,
5289
+ short_id: task.short_id,
5290
+ title: task.title,
5291
+ description: task.description,
5292
+ status: task.status,
5293
+ priority: task.priority,
5294
+ project_id: task.project_id,
5295
+ parent_id: task.parent_id,
5296
+ plan_id: task.plan_id,
5297
+ task_list_id: task.task_list_id,
5298
+ agent_id: task.agent_id,
5299
+ assigned_to: task.assigned_to,
5300
+ session_id: task.session_id,
5301
+ working_dir: task.working_dir,
5302
+ tags: task.tags,
5303
+ metadata: task.metadata,
5304
+ version: task.version,
5305
+ created_at: task.created_at,
5306
+ updated_at: task.updated_at,
5307
+ started_at: task.started_at,
5308
+ completed_at: task.completed_at,
5309
+ due_at: task.due_at,
5310
+ requires_approval: task.requires_approval,
5311
+ approved_by: task.approved_by,
5312
+ approved_at: task.approved_at,
5313
+ ...extra
5314
+ };
5315
+ }
5266
5316
  function taskEventMetadata(task) {
5267
5317
  const metadata = {
5268
5318
  package: "@hasna/todos",
5269
5319
  todos_event_schema_version: 1,
5320
+ route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
5270
5321
  task_id: task.id,
5271
5322
  task_short_id: task.short_id,
5272
5323
  project_id: task.project_id,
5273
5324
  task_list_id: task.task_list_id,
5274
5325
  working_dir: task.working_dir
5275
5326
  };
5276
- const routeEnabled = booleanField(task.metadata.route_enabled);
5277
- if (routeEnabled !== undefined) {
5278
- metadata.route_enabled = routeEnabled;
5279
- }
5280
- const automation = routingAutomationMetadata(task);
5281
- if (automation) {
5282
- metadata.automation = automation;
5327
+ const pointers = workflowPointersFromMetadata(task.metadata);
5328
+ for (const [key, value] of Object.entries(pointers)) {
5329
+ if (value)
5330
+ metadata[key] = value;
5283
5331
  }
5284
5332
  try {
5285
5333
  const project = task.project_id ? getProject(task.project_id) : null;
@@ -5308,18 +5356,20 @@ function taskEventMetadata(task) {
5308
5356
  metadata.task_list_project_id = taskList.project_id;
5309
5357
  metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
5310
5358
  }
5359
+ const routeEnabled = routeEnabledForTask(task, taskList);
5360
+ if (routeEnabled !== undefined) {
5361
+ metadata.route_enabled = routeEnabled;
5362
+ }
5363
+ const automation = routingAutomationMetadata(task, taskList);
5364
+ if (automation) {
5365
+ metadata.automation = automation;
5366
+ metadata.route_blocked_by_no_auto = automation.no_auto === true;
5367
+ metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
5368
+ metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
5369
+ }
5311
5370
  } catch {}
5312
5371
  return metadata;
5313
5372
  }
5314
- function classifyProjectKind(path) {
5315
- return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
5316
- }
5317
- function isWorktreePath(path) {
5318
- return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
5319
- }
5320
- function inferRootProjectId(project) {
5321
- return isWorktreePath(project.path) ? null : project.id;
5322
- }
5323
5373
  function readMachineLocalPath(project) {
5324
5374
  const machineId = process.env["TODOS_MACHINE_ID"];
5325
5375
  if (!machineId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.11.65",
3
+ "version": "0.11.66",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",