@hasna/todos 0.9.65 → 0.9.67
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 +69 -0
- package/dist/server/index.js +13 -0
- package/dist/server/serve.d.ts.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -11952,6 +11966,19 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
11952
11966
|
return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
|
|
11953
11967
|
}
|
|
11954
11968
|
}
|
|
11969
|
+
if (path === "/api/doctor" && method === "GET") {
|
|
11970
|
+
const issues = [];
|
|
11971
|
+
const { getStaleTasks: getStaleDiag } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
11972
|
+
const staleItems = getStaleDiag(30);
|
|
11973
|
+
if (staleItems.length > 0)
|
|
11974
|
+
issues.push({ severity: "warn", type: "stale_tasks", message: `${staleItems.length} tasks stuck in_progress >30min`, count: staleItems.length });
|
|
11975
|
+
const withParent = getDatabase().query("SELECT COUNT(*) as c FROM tasks t WHERE t.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tasks p WHERE p.id = t.parent_id)").get();
|
|
11976
|
+
if (withParent.c > 0)
|
|
11977
|
+
issues.push({ severity: "error", type: "orphaned_parents", message: `${withParent.c} tasks reference non-existent parent IDs`, count: withParent.c });
|
|
11978
|
+
if (issues.length === 0)
|
|
11979
|
+
issues.push({ severity: "info", type: "healthy", message: "No issues found" });
|
|
11980
|
+
return json({ ok: !issues.some((i) => i.severity === "error"), issues }, 200, port);
|
|
11981
|
+
}
|
|
11955
11982
|
if (path === "/api/report" && method === "GET") {
|
|
11956
11983
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
11957
11984
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
@@ -15028,6 +15055,48 @@ program2.command("summary").description("Generate a markdown summary of recent t
|
|
|
15028
15055
|
console.log(lines.join(`
|
|
15029
15056
|
`));
|
|
15030
15057
|
});
|
|
15058
|
+
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) => {
|
|
15059
|
+
const globalOpts = program2.opts();
|
|
15060
|
+
const db = getDatabase();
|
|
15061
|
+
const issues = [];
|
|
15062
|
+
const stale = listTasks({ status: "in_progress" }).filter((t) => new Date(t.updated_at).getTime() < Date.now() - 30 * 60 * 1000);
|
|
15063
|
+
if (stale.length > 0)
|
|
15064
|
+
issues.push({ severity: "warn", type: "stale_tasks", message: `${stale.length} tasks stuck in_progress >30min`, count: stale.length });
|
|
15065
|
+
const { isValidRecurrenceRule: isValidRecurrenceRule2 } = (init_recurrence(), __toCommonJS(exports_recurrence));
|
|
15066
|
+
const recurring = listTasks({ status: ["pending", "in_progress"] }).filter((t) => t.recurrence_rule);
|
|
15067
|
+
const invalidRecurrence = recurring.filter((t) => !isValidRecurrenceRule2(t.recurrence_rule));
|
|
15068
|
+
if (invalidRecurrence.length > 0)
|
|
15069
|
+
issues.push({ severity: "error", type: "invalid_recurrence", message: `${invalidRecurrence.length} tasks with invalid recurrence_rule`, count: invalidRecurrence.length });
|
|
15070
|
+
const nowStr = new Date().toISOString();
|
|
15071
|
+
const overdueRecurring = recurring.filter((t) => t.due_at && t.due_at < nowStr);
|
|
15072
|
+
if (overdueRecurring.length > 0)
|
|
15073
|
+
issues.push({ severity: "warn", type: "overdue_recurring", message: `${overdueRecurring.length} recurring tasks past due date`, count: overdueRecurring.length });
|
|
15074
|
+
const allIds = new Set(listTasks({}).map((t) => t.id));
|
|
15075
|
+
const withParent = db.query("SELECT id, parent_id FROM tasks WHERE parent_id IS NOT NULL").all();
|
|
15076
|
+
const orphaned = withParent.filter((t) => !allIds.has(t.parent_id));
|
|
15077
|
+
if (orphaned.length > 0)
|
|
15078
|
+
issues.push({ severity: "error", type: "orphaned_parents", message: `${orphaned.length} tasks reference non-existent parent IDs`, count: orphaned.length });
|
|
15079
|
+
if (issues.length === 0)
|
|
15080
|
+
issues.push({ severity: "info", type: "healthy", message: "No issues found" });
|
|
15081
|
+
if (opts.json || globalOpts.json) {
|
|
15082
|
+
console.log(JSON.stringify({ issues, ok: !issues.some((i) => i.severity === "error") }));
|
|
15083
|
+
return;
|
|
15084
|
+
}
|
|
15085
|
+
console.log(chalk.bold(`todos doctor
|
|
15086
|
+
`));
|
|
15087
|
+
for (const issue of issues) {
|
|
15088
|
+
const icon = issue.severity === "error" ? chalk.red("\u2717") : issue.severity === "warn" ? chalk.yellow("\u26A0") : chalk.green("\u2713");
|
|
15089
|
+
console.log(` ${icon} ${issue.message}`);
|
|
15090
|
+
}
|
|
15091
|
+
const errors2 = issues.filter((i) => i.severity === "error").length;
|
|
15092
|
+
const warns = issues.filter((i) => i.severity === "warn").length;
|
|
15093
|
+
if (errors2 === 0 && warns === 0)
|
|
15094
|
+
console.log(chalk.green(`
|
|
15095
|
+
All clear.`));
|
|
15096
|
+
else
|
|
15097
|
+
console.log(chalk[errors2 > 0 ? "red" : "yellow"](`
|
|
15098
|
+
${errors2} error(s), ${warns} warning(s). Run with --fix to auto-resolve where possible.`));
|
|
15099
|
+
});
|
|
15031
15100
|
program2.command("health").description("Check todos system health \u2014 database, config, connectivity").option("--json", "Output as JSON").action(async (opts) => {
|
|
15032
15101
|
const globalOpts = program2.opts();
|
|
15033
15102
|
const checks = [];
|
package/dist/server/index.js
CHANGED
|
@@ -3105,6 +3105,19 @@ data: ${JSON.stringify({ type: "connected", agent_id: agentId, timestamp: new Da
|
|
|
3105
3105
|
return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
|
|
3106
3106
|
}
|
|
3107
3107
|
}
|
|
3108
|
+
if (path === "/api/doctor" && method === "GET") {
|
|
3109
|
+
const issues = [];
|
|
3110
|
+
const { getStaleTasks: getStaleDiag } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
|
|
3111
|
+
const staleItems = getStaleDiag(30);
|
|
3112
|
+
if (staleItems.length > 0)
|
|
3113
|
+
issues.push({ severity: "warn", type: "stale_tasks", message: `${staleItems.length} tasks stuck in_progress >30min`, count: staleItems.length });
|
|
3114
|
+
const withParent = getDatabase().query("SELECT COUNT(*) as c FROM tasks t WHERE t.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tasks p WHERE p.id = t.parent_id)").get();
|
|
3115
|
+
if (withParent.c > 0)
|
|
3116
|
+
issues.push({ severity: "error", type: "orphaned_parents", message: `${withParent.c} tasks reference non-existent parent IDs`, count: withParent.c });
|
|
3117
|
+
if (issues.length === 0)
|
|
3118
|
+
issues.push({ severity: "info", type: "healthy", message: "No issues found" });
|
|
3119
|
+
return json({ ok: !issues.some((i) => i.severity === "error"), issues }, 200, port);
|
|
3120
|
+
}
|
|
3108
3121
|
if (path === "/api/report" && method === "GET") {
|
|
3109
3122
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
3110
3123
|
const projectId = url.searchParams.get("project_id") || undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiHH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/server/serve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiHH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAu1B1G"}
|