@hasna/todos 0.9.64 → 0.9.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.
package/dist/cli/index.js CHANGED
@@ -3030,6 +3030,12 @@ var init_audit = __esm(() => {
3030
3030
  });
3031
3031
 
3032
3032
  // src/lib/recurrence.ts
3033
+ var exports_recurrence = {};
3034
+ __export(exports_recurrence, {
3035
+ parseRecurrenceRule: () => parseRecurrenceRule,
3036
+ nextOccurrence: () => nextOccurrence,
3037
+ isValidRecurrenceRule: () => isValidRecurrenceRule
3038
+ });
3033
3039
  function parseRecurrenceRule(rule) {
3034
3040
  const normalized = rule.trim().toLowerCase();
3035
3041
  if (normalized === "every weekday" || normalized === "every weekdays") {
@@ -3068,6 +3074,14 @@ function parseRecurrenceRule(rule) {
3068
3074
  }
3069
3075
  throw new Error(`Invalid recurrence rule: "${rule}". Supported formats: "every day", "every weekday", "every week", "every 2 weeks", "every month", "every N days/weeks/months", "every monday", "every mon,wed,fri"`);
3070
3076
  }
3077
+ function isValidRecurrenceRule(rule) {
3078
+ try {
3079
+ parseRecurrenceRule(rule);
3080
+ return true;
3081
+ } catch {
3082
+ return false;
3083
+ }
3084
+ }
3071
3085
  function nextOccurrence(rule, from) {
3072
3086
  const parsed = parseRecurrenceRule(rule);
3073
3087
  const base = from || new Date;
@@ -9304,6 +9318,7 @@ var init_mcp = __esm(() => {
9304
9318
  "complete_task",
9305
9319
  "fail_task",
9306
9320
  "get_status",
9321
+ "get_context",
9307
9322
  "get_task",
9308
9323
  "start_task",
9309
9324
  "add_comment",
@@ -10893,6 +10908,37 @@ No pending tasks available.`);
10893
10908
  }
10894
10909
  });
10895
10910
  }
10911
+ if (shouldRegisterTool("get_context")) {
10912
+ server.tool("get_context", "Get a compact task summary for agent prompt injection. Returns formatted text.", {
10913
+ agent_id: exports_external.string().optional(),
10914
+ project_id: exports_external.string().optional(),
10915
+ format: exports_external.enum(["text", "compact"]).optional()
10916
+ }, async ({ agent_id, project_id, format: _format }) => {
10917
+ try {
10918
+ const filters = {};
10919
+ if (project_id)
10920
+ filters.project_id = resolveId(project_id, "projects");
10921
+ const status = getStatus(Object.keys(filters).length > 0 ? filters : undefined, agent_id);
10922
+ const next = getNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
10923
+ const lines = [];
10924
+ lines.push(`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
10925
+ if (status.stale_count > 0)
10926
+ lines.push(`\u26A0 ${status.stale_count} stale tasks`);
10927
+ if (status.overdue_recurring > 0)
10928
+ lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
10929
+ if (status.active_work.length > 0) {
10930
+ const active = status.active_work.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ");
10931
+ lines.push(`Active: ${active}`);
10932
+ }
10933
+ if (next)
10934
+ lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
10935
+ return { content: [{ type: "text", text: lines.join(`
10936
+ `) }] };
10937
+ } catch (e) {
10938
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
10939
+ }
10940
+ });
10941
+ }
10896
10942
  if (shouldRegisterTool("search_tools")) {
10897
10943
  server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
10898
10944
  const all = [
@@ -10954,6 +11000,7 @@ No pending tasks available.`);
10954
11000
  "get_tasks_changed_since",
10955
11001
  "get_stale_tasks",
10956
11002
  "get_status",
11003
+ "get_context",
10957
11004
  "decompose_task",
10958
11005
  "set_task_status",
10959
11006
  "set_task_priority",
@@ -14995,6 +15042,48 @@ program2.command("summary").description("Generate a markdown summary of recent t
14995
15042
  console.log(lines.join(`
14996
15043
  `));
14997
15044
  });
15045
+ program2.command("doctor").description("Diagnose common task data issues").option("--fix", "Auto-fix recoverable issues where possible").option("--json", "Output as JSON").action(async (opts) => {
15046
+ const globalOpts = program2.opts();
15047
+ const db = getDatabase();
15048
+ const issues = [];
15049
+ const stale = listTasks({ status: "in_progress" }).filter((t) => new Date(t.updated_at).getTime() < Date.now() - 30 * 60 * 1000);
15050
+ if (stale.length > 0)
15051
+ issues.push({ severity: "warn", type: "stale_tasks", message: `${stale.length} tasks stuck in_progress >30min`, count: stale.length });
15052
+ const { isValidRecurrenceRule: isValidRecurrenceRule2 } = (init_recurrence(), __toCommonJS(exports_recurrence));
15053
+ const recurring = listTasks({ status: ["pending", "in_progress"] }).filter((t) => t.recurrence_rule);
15054
+ const invalidRecurrence = recurring.filter((t) => !isValidRecurrenceRule2(t.recurrence_rule));
15055
+ if (invalidRecurrence.length > 0)
15056
+ issues.push({ severity: "error", type: "invalid_recurrence", message: `${invalidRecurrence.length} tasks with invalid recurrence_rule`, count: invalidRecurrence.length });
15057
+ const nowStr = new Date().toISOString();
15058
+ const overdueRecurring = recurring.filter((t) => t.due_at && t.due_at < nowStr);
15059
+ if (overdueRecurring.length > 0)
15060
+ issues.push({ severity: "warn", type: "overdue_recurring", message: `${overdueRecurring.length} recurring tasks past due date`, count: overdueRecurring.length });
15061
+ const allIds = new Set(listTasks({}).map((t) => t.id));
15062
+ const withParent = db.query("SELECT id, parent_id FROM tasks WHERE parent_id IS NOT NULL").all();
15063
+ const orphaned = withParent.filter((t) => !allIds.has(t.parent_id));
15064
+ if (orphaned.length > 0)
15065
+ issues.push({ severity: "error", type: "orphaned_parents", message: `${orphaned.length} tasks reference non-existent parent IDs`, count: orphaned.length });
15066
+ if (issues.length === 0)
15067
+ issues.push({ severity: "info", type: "healthy", message: "No issues found" });
15068
+ if (opts.json || globalOpts.json) {
15069
+ console.log(JSON.stringify({ issues, ok: !issues.some((i) => i.severity === "error") }));
15070
+ return;
15071
+ }
15072
+ console.log(chalk.bold(`todos doctor
15073
+ `));
15074
+ for (const issue of issues) {
15075
+ const icon = issue.severity === "error" ? chalk.red("\u2717") : issue.severity === "warn" ? chalk.yellow("\u26A0") : chalk.green("\u2713");
15076
+ console.log(` ${icon} ${issue.message}`);
15077
+ }
15078
+ const errors2 = issues.filter((i) => i.severity === "error").length;
15079
+ const warns = issues.filter((i) => i.severity === "warn").length;
15080
+ if (errors2 === 0 && warns === 0)
15081
+ console.log(chalk.green(`
15082
+ All clear.`));
15083
+ else
15084
+ console.log(chalk[errors2 > 0 ? "red" : "yellow"](`
15085
+ ${errors2} error(s), ${warns} warning(s). Run with --fix to auto-resolve where possible.`));
15086
+ });
14998
15087
  program2.command("health").description("Check todos system health \u2014 database, config, connectivity").option("--json", "Output as JSON").action(async (opts) => {
14999
15088
  const globalOpts = program2.opts();
15000
15089
  const checks = [];
package/dist/mcp/index.js CHANGED
@@ -6970,6 +6970,7 @@ var MINIMAL_TOOLS = new Set([
6970
6970
  "complete_task",
6971
6971
  "fail_task",
6972
6972
  "get_status",
6973
+ "get_context",
6973
6974
  "get_task",
6974
6975
  "start_task",
6975
6976
  "add_comment",
@@ -8658,6 +8659,37 @@ if (shouldRegisterTool("set_task_priority")) {
8658
8659
  }
8659
8660
  });
8660
8661
  }
8662
+ if (shouldRegisterTool("get_context")) {
8663
+ server.tool("get_context", "Get a compact task summary for agent prompt injection. Returns formatted text.", {
8664
+ agent_id: exports_external.string().optional(),
8665
+ project_id: exports_external.string().optional(),
8666
+ format: exports_external.enum(["text", "compact"]).optional()
8667
+ }, async ({ agent_id, project_id, format: _format }) => {
8668
+ try {
8669
+ const filters = {};
8670
+ if (project_id)
8671
+ filters.project_id = resolveId(project_id, "projects");
8672
+ const status = getStatus(Object.keys(filters).length > 0 ? filters : undefined, agent_id);
8673
+ const next = getNextTask(agent_id, Object.keys(filters).length > 0 ? filters : undefined);
8674
+ const lines = [];
8675
+ lines.push(`Tasks: ${status.pending} pending | ${status.in_progress} active | ${status.completed} done`);
8676
+ if (status.stale_count > 0)
8677
+ lines.push(`\u26A0 ${status.stale_count} stale tasks`);
8678
+ if (status.overdue_recurring > 0)
8679
+ lines.push(`\uD83D\uDD01 ${status.overdue_recurring} overdue recurring`);
8680
+ if (status.active_work.length > 0) {
8681
+ const active = status.active_work.slice(0, 3).map((w) => `${w.short_id || w.id.slice(0, 8)} (${w.assigned_to || "?"})`).join(", ");
8682
+ lines.push(`Active: ${active}`);
8683
+ }
8684
+ if (next)
8685
+ lines.push(`Next up: ${next.short_id || next.id.slice(0, 8)} [${next.priority}] ${next.title}`);
8686
+ return { content: [{ type: "text", text: lines.join(`
8687
+ `) }] };
8688
+ } catch (e) {
8689
+ return { content: [{ type: "text", text: formatError(e) }], isError: true };
8690
+ }
8691
+ });
8692
+ }
8661
8693
  if (shouldRegisterTool("search_tools")) {
8662
8694
  server.tool("search_tools", "List all tool names, optionally filtered by substring.", { query: exports_external.string().optional() }, async ({ query }) => {
8663
8695
  const all = [
@@ -8719,6 +8751,7 @@ if (shouldRegisterTool("search_tools")) {
8719
8751
  "get_tasks_changed_since",
8720
8752
  "get_stale_tasks",
8721
8753
  "get_status",
8754
+ "get_context",
8722
8755
  "decompose_task",
8723
8756
  "set_task_status",
8724
8757
  "set_task_priority",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.64",
3
+ "version": "0.9.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",