@hasna/todos 0.11.74 → 0.11.75
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/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +736 -75
- package/dist/lib/routing-doctor.d.ts +144 -0
- package/dist/lib/routing-doctor.d.ts.map +1 -0
- package/dist/release-provenance.json +3 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -13277,6 +13277,19 @@ function buildExpectationMetadata(opts) {
|
|
|
13277
13277
|
metadata["acceptance"] = parseJsonValue(String(acceptance));
|
|
13278
13278
|
return metadata;
|
|
13279
13279
|
}
|
|
13280
|
+
function resolveTaskListRef(ref, projectId) {
|
|
13281
|
+
const db = getDatabase();
|
|
13282
|
+
const exact = getTaskList(ref, db);
|
|
13283
|
+
if (exact)
|
|
13284
|
+
return { id: exact.id };
|
|
13285
|
+
const partial = resolvePartialId(db, "task_lists", ref);
|
|
13286
|
+
if (partial)
|
|
13287
|
+
return { id: partial };
|
|
13288
|
+
const bySlug = getTaskListBySlug(ref, projectId ?? undefined, db);
|
|
13289
|
+
if (bySlug)
|
|
13290
|
+
return { id: bySlug.id };
|
|
13291
|
+
return { error: `Could not resolve task list "${ref}" to a UUID${projectId ? " within the task's project" : ""}. Pass an exact task-list UUID.` };
|
|
13292
|
+
}
|
|
13280
13293
|
function registerTaskCommands(program2) {
|
|
13281
13294
|
program2.command("add <title>").description("Create a new task").option("-d, --description <text>", "Task description").option("-p, --priority <level>", "Priority: low, medium, high, critical").option("--parent <id>", "Parent task ID").option("-t, --tags <tags>", "Comma-separated tags").option("--tag <tags>", "Comma-separated tags (alias for --tags)").option("--plan <id>", "Assign to a plan").option("--assign <agent>", "Assign to agent").option("--status <status>", "Initial status").option("--list <id>", "Task list ID").option("--task-list <id>", "Task list ID (alias for --list)").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--approval", "Require approval before completion").option("--recurrence <rule>", "Recurrence rule, e.g. 'every day', 'every weekday', 'every 2 weeks'").option("--due <date>", "Due date (ISO string or YYYY-MM-DD)").option("--reason <text>", "Why this task exists").option("--project <id>", "Assign to project by ID or slug (overrides auto-detect)").action((title, opts) => {
|
|
13282
13295
|
const globalOpts = program2.opts();
|
|
@@ -13830,7 +13843,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
13830
13843
|
console.log(` ${chalk2.dim(h.created_at)} ${chalk2.bold(h.action)}${field}${change}${agent}`);
|
|
13831
13844
|
}
|
|
13832
13845
|
});
|
|
13833
|
-
program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list").option("--task-list <id>", "Move to a task list (alias for --list)").option("--plan <id>", "Move to a plan").option("--clear-plan", "Remove from its current plan").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--due <date>", "Due date (ISO string or YYYY-MM-DD), empty to clear").option("--recurrence <rule>", "Recurrence rule, empty to clear").option("--approval", "Require approval before completion").option("--clear-approval", "Remove the approval requirement").action((id, opts) => {
|
|
13846
|
+
program2.command("update <id>").description("Update a task").option("--title <text>", "New title").option("-d, --description <text>", "New description").option("-s, --status <status>", "New status").option("-p, --priority <priority>", "New priority").option("--assign <agent>", "Assign to agent").option("--tags <tags>", "New tags (comma-separated)").option("--tag <tags>", "New tags (alias for --tags)").option("--list <id>", "Move to a task list (UUID authoritative; project-scoped slug accepted)").option("--task-list <id>", "Move to a task list (alias for --list)").option("--clear-list", "Detach from its task list (reset task_list_id to null)").option("--working-dir <path>", "Repair the task's working_dir to a specific path (routing metadata)").option("--clear-working-dir", "Reset the task's working_dir to null (undo path for routing repairs)").option("--plan <id>", "Move to a plan").option("--clear-plan", "Remove from its current plan").option("--estimated <minutes>", "Estimated time in minutes").option("--sla-minutes <minutes>", "SLA minutes before unfinished work is escalated").option("--sla <minutes>", "Alias for --sla-minutes").option("--due <date>", "Due date (ISO string or YYYY-MM-DD), empty to clear").option("--recurrence <rule>", "Recurrence rule, empty to clear").option("--approval", "Require approval before completion").option("--clear-approval", "Remove the approval requirement").action((id, opts) => {
|
|
13834
13847
|
const globalOpts = program2.opts();
|
|
13835
13848
|
opts.tags = opts.tags || opts.tag;
|
|
13836
13849
|
opts.list = opts.list || opts.taskList;
|
|
@@ -13848,15 +13861,22 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
13848
13861
|
console.error(chalk2.red("Use either --approval or --clear-approval, not both."));
|
|
13849
13862
|
process.exit(1);
|
|
13850
13863
|
}
|
|
13864
|
+
if (opts.list && opts.clearList) {
|
|
13865
|
+
console.error(chalk2.red("Use either --list or --clear-list, not both."));
|
|
13866
|
+
process.exit(1);
|
|
13867
|
+
}
|
|
13868
|
+
if (opts.workingDir !== undefined && opts.clearWorkingDir) {
|
|
13869
|
+
console.error(chalk2.red("Use either --working-dir or --clear-working-dir, not both."));
|
|
13870
|
+
process.exit(1);
|
|
13871
|
+
}
|
|
13851
13872
|
const taskListId = opts.list ? (() => {
|
|
13852
|
-
const
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
console.error(chalk2.red(`Could not resolve task list ID: ${opts.list}`));
|
|
13873
|
+
const resolved = resolveTaskListRef(opts.list, current.project_id);
|
|
13874
|
+
if ("error" in resolved) {
|
|
13875
|
+
console.error(chalk2.red(resolved.error));
|
|
13856
13876
|
process.exit(1);
|
|
13857
13877
|
}
|
|
13858
|
-
return resolved;
|
|
13859
|
-
})() : undefined;
|
|
13878
|
+
return resolved.id;
|
|
13879
|
+
})() : opts.clearList ? null : undefined;
|
|
13860
13880
|
const planId = opts.plan ? resolvePlanId(opts.plan) : opts.clearPlan ? null : undefined;
|
|
13861
13881
|
let task2;
|
|
13862
13882
|
try {
|
|
@@ -13870,6 +13890,7 @@ ${chalk2.cyan(sid)} ${statusColor(task2.status)} ${prioColor(task2.priority)} ${
|
|
|
13870
13890
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
13871
13891
|
plan_id: planId,
|
|
13872
13892
|
task_list_id: taskListId,
|
|
13893
|
+
working_dir: opts.workingDir ? resolve9(opts.workingDir) : opts.clearWorkingDir ? null : undefined,
|
|
13873
13894
|
estimated_minutes: opts.estimated !== undefined ? parseIntOption(opts.estimated, "--estimated") : undefined,
|
|
13874
13895
|
sla_minutes: opts.slaMinutes !== undefined || opts.sla !== undefined ? parseIntOption(opts.slaMinutes ?? opts.sla, "--sla-minutes") : undefined,
|
|
13875
13896
|
due_at: opts.due !== undefined ? opts.due === "" ? null : opts.due.length === 10 ? opts.due + "T00:00:00.000Z" : opts.due : undefined,
|
|
@@ -14069,6 +14090,7 @@ var init_task_commands = __esm(() => {
|
|
|
14069
14090
|
init_database();
|
|
14070
14091
|
init_projects();
|
|
14071
14092
|
init_tasks();
|
|
14093
|
+
init_task_lists();
|
|
14072
14094
|
init_helpers();
|
|
14073
14095
|
init_types();
|
|
14074
14096
|
});
|
|
@@ -55591,6 +55613,523 @@ var init_config_serve_commands = __esm(() => {
|
|
|
55591
55613
|
init_helpers();
|
|
55592
55614
|
});
|
|
55593
55615
|
|
|
55616
|
+
// src/lib/routing-doctor.ts
|
|
55617
|
+
var exports_routing_doctor = {};
|
|
55618
|
+
__export(exports_routing_doctor, {
|
|
55619
|
+
runRoutingDoctor: () => runRoutingDoctor,
|
|
55620
|
+
routingShardOf: () => routingShardOf,
|
|
55621
|
+
routingRepairUndoCommand: () => routingRepairUndoCommand,
|
|
55622
|
+
detectCrossRepoIntent: () => detectCrossRepoIntent,
|
|
55623
|
+
classifyTaskRouting: () => classifyTaskRouting,
|
|
55624
|
+
TODOS_ROUTING_DOCTOR_SCHEMA_VERSION: () => TODOS_ROUTING_DOCTOR_SCHEMA_VERSION
|
|
55625
|
+
});
|
|
55626
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync22, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
55627
|
+
import { basename as basename9, dirname as dirname12, join as join21 } from "path";
|
|
55628
|
+
function normalizePath5(path) {
|
|
55629
|
+
if (!path)
|
|
55630
|
+
return null;
|
|
55631
|
+
const trimmed = path.replace(/\/+$/, "");
|
|
55632
|
+
return trimmed === "" ? "/" : trimmed;
|
|
55633
|
+
}
|
|
55634
|
+
function looksLikeRemoteUri(path) {
|
|
55635
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(path);
|
|
55636
|
+
}
|
|
55637
|
+
function looksLikeLocalAbsolutePath(path) {
|
|
55638
|
+
return path.startsWith("/") && !looksLikeRemoteUri(path);
|
|
55639
|
+
}
|
|
55640
|
+
function looksLikeMacHomePath(path) {
|
|
55641
|
+
return /^\/Users\//.test(path);
|
|
55642
|
+
}
|
|
55643
|
+
function stableHash2(key) {
|
|
55644
|
+
let hash2 = 2166136261;
|
|
55645
|
+
for (let i = 0;i < key.length; i++) {
|
|
55646
|
+
hash2 ^= key.charCodeAt(i);
|
|
55647
|
+
hash2 = Math.imul(hash2, 16777619);
|
|
55648
|
+
}
|
|
55649
|
+
return hash2 >>> 0;
|
|
55650
|
+
}
|
|
55651
|
+
function routingShardOf(task2, total) {
|
|
55652
|
+
if (total <= 1)
|
|
55653
|
+
return 0;
|
|
55654
|
+
const key = task2.project_id ?? task2.id;
|
|
55655
|
+
return stableHash2(key) % total;
|
|
55656
|
+
}
|
|
55657
|
+
function repoSlugForProject(project) {
|
|
55658
|
+
const fromPath = project.path && looksLikeLocalAbsolutePath(project.path) ? basename9(normalizePath5(project.path) ?? "") : "";
|
|
55659
|
+
if (fromPath)
|
|
55660
|
+
return fromPath.toLowerCase();
|
|
55661
|
+
return project.name.trim().toLowerCase().replace(/\s+/g, "-");
|
|
55662
|
+
}
|
|
55663
|
+
function detectCrossRepoIntent(task2, project, knownRepoSlugs) {
|
|
55664
|
+
if (!project)
|
|
55665
|
+
return null;
|
|
55666
|
+
const ownSlug = repoSlugForProject(project);
|
|
55667
|
+
const named = new Set;
|
|
55668
|
+
for (const match of task2.title.matchAll(REPO_NAME_RE))
|
|
55669
|
+
named.add(match[0].toLowerCase());
|
|
55670
|
+
for (const tag of task2.tags) {
|
|
55671
|
+
const m = /^repo:(.+)$/i.exec(tag);
|
|
55672
|
+
if (m && m[1])
|
|
55673
|
+
named.add(m[1].trim().toLowerCase());
|
|
55674
|
+
}
|
|
55675
|
+
if (named.size === 0)
|
|
55676
|
+
return null;
|
|
55677
|
+
if (named.has(ownSlug))
|
|
55678
|
+
return null;
|
|
55679
|
+
for (const candidate of named) {
|
|
55680
|
+
if (candidate !== ownSlug && knownRepoSlugs.has(candidate))
|
|
55681
|
+
return candidate;
|
|
55682
|
+
}
|
|
55683
|
+
return null;
|
|
55684
|
+
}
|
|
55685
|
+
function bump(map, key) {
|
|
55686
|
+
map[key] = (map[key] ?? 0) + 1;
|
|
55687
|
+
}
|
|
55688
|
+
function resolveCanonicalTaskList(project, db) {
|
|
55689
|
+
if (!project?.task_list_id)
|
|
55690
|
+
return null;
|
|
55691
|
+
return getTaskList(project.task_list_id, db) ?? getTaskListBySlug(project.task_list_id, project.id, db);
|
|
55692
|
+
}
|
|
55693
|
+
function taskListState(task2, project, db) {
|
|
55694
|
+
if (!task2.task_list_id)
|
|
55695
|
+
return "null";
|
|
55696
|
+
const resolved = getTaskList(task2.task_list_id, db) ?? (project ? getTaskListBySlug(task2.task_list_id, project.id, db) : null);
|
|
55697
|
+
return resolved ? "ok" : "unresolvable";
|
|
55698
|
+
}
|
|
55699
|
+
function classifyTaskRouting(ctx) {
|
|
55700
|
+
return evaluateTaskRouting(ctx).findings;
|
|
55701
|
+
}
|
|
55702
|
+
function evaluateTaskRouting(ctx) {
|
|
55703
|
+
const { task: task2, project, db, knownRepoSlugs, verifyProjectRoot } = ctx;
|
|
55704
|
+
const rs = getTaskRouteState(task2, db, { verifyProjectRoot });
|
|
55705
|
+
const findings = [];
|
|
55706
|
+
const projectPath = normalizePath5(rs.route.project_path);
|
|
55707
|
+
const workingDir = task2.working_dir ? normalizePath5(task2.working_dir) : null;
|
|
55708
|
+
const projectRootExists = rs.evidence.project_root_exists;
|
|
55709
|
+
const routeOptIn = rs.gates.tag_opt_in || rs.gates.route_enabled;
|
|
55710
|
+
const crossRepoForeign = detectCrossRepoIntent(task2, project, knownRepoSlugs);
|
|
55711
|
+
const base = {
|
|
55712
|
+
task_id: task2.id,
|
|
55713
|
+
task_short_id: task2.short_id,
|
|
55714
|
+
title: task2.title,
|
|
55715
|
+
status: task2.status,
|
|
55716
|
+
project_id: rs.route.project_id,
|
|
55717
|
+
project_name: project?.name ?? null,
|
|
55718
|
+
project_path: rs.route.project_path,
|
|
55719
|
+
working_dir: task2.working_dir,
|
|
55720
|
+
task_list_id: task2.task_list_id,
|
|
55721
|
+
route_eligible: rs.eligible,
|
|
55722
|
+
route_class: rs.route_class,
|
|
55723
|
+
route_reasons: rs.reasons
|
|
55724
|
+
};
|
|
55725
|
+
const listState = taskListState(task2, project, db);
|
|
55726
|
+
const pathIsLocal = projectPath ? looksLikeLocalAbsolutePath(projectPath) : false;
|
|
55727
|
+
const pathInvalidOnMachine = verifyProjectRoot && pathIsLocal && projectRootExists === false;
|
|
55728
|
+
const projectMissingPath = project !== null && (!projectPath || pathIsLocal === false && !looksLikeRemoteUri(projectPath ?? ""));
|
|
55729
|
+
if (!project && !task2.project_id) {
|
|
55730
|
+
if (routeOptIn && !workingDir) {
|
|
55731
|
+
findings.push({
|
|
55732
|
+
...base,
|
|
55733
|
+
category: "missing_project",
|
|
55734
|
+
severity: "warn",
|
|
55735
|
+
repair_class: "unsupported",
|
|
55736
|
+
expected_working_dir: null,
|
|
55737
|
+
task_list_state: listState,
|
|
55738
|
+
detail: "Route-opted-in task has no project and no working_dir; cannot derive an owning repo path.",
|
|
55739
|
+
suggested_repair: null
|
|
55740
|
+
});
|
|
55741
|
+
}
|
|
55742
|
+
} else if (pathInvalidOnMachine || projectMissingPath) {
|
|
55743
|
+
const macHome = projectPath ? looksLikeMacHomePath(projectPath) : false;
|
|
55744
|
+
findings.push({
|
|
55745
|
+
...base,
|
|
55746
|
+
category: "invalid_project_path",
|
|
55747
|
+
severity: "warn",
|
|
55748
|
+
repair_class: "blocker_invalid_path",
|
|
55749
|
+
expected_working_dir: null,
|
|
55750
|
+
task_list_state: listState,
|
|
55751
|
+
detail: projectMissingPath ? "Owning project has no usable local/remote path." : `Owning project path does not exist on this machine${macHome ? " (macOS home path \u2014 likely valid on its home Mac)" : ""}. Reported, not rewritten.`,
|
|
55752
|
+
suggested_repair: null
|
|
55753
|
+
});
|
|
55754
|
+
}
|
|
55755
|
+
const haveGoodProjectPath = Boolean(projectPath && pathIsLocal && projectRootExists === true);
|
|
55756
|
+
if (project && projectPath) {
|
|
55757
|
+
if (!workingDir) {
|
|
55758
|
+
if (crossRepoForeign) {
|
|
55759
|
+
findings.push({
|
|
55760
|
+
...base,
|
|
55761
|
+
category: "null_working_dir",
|
|
55762
|
+
severity: "warn",
|
|
55763
|
+
repair_class: "blocker_cross_repo",
|
|
55764
|
+
expected_working_dir: rs.route.project_path,
|
|
55765
|
+
task_list_state: listState,
|
|
55766
|
+
detail: `working_dir is empty and title names a different repo (${crossRepoForeign}); resolve project ownership before setting a path.`,
|
|
55767
|
+
suggested_repair: null
|
|
55768
|
+
});
|
|
55769
|
+
} else if (haveGoodProjectPath) {
|
|
55770
|
+
findings.push({
|
|
55771
|
+
...base,
|
|
55772
|
+
category: "null_working_dir",
|
|
55773
|
+
severity: "error",
|
|
55774
|
+
repair_class: "safe_auto",
|
|
55775
|
+
expected_working_dir: rs.route.project_path,
|
|
55776
|
+
task_list_state: listState,
|
|
55777
|
+
detail: "working_dir is empty; set it to the owning project path.",
|
|
55778
|
+
suggested_repair: {
|
|
55779
|
+
field: "working_dir",
|
|
55780
|
+
from: null,
|
|
55781
|
+
to: rs.route.project_path,
|
|
55782
|
+
command: `todos update ${task2.id} --working-dir ${rs.route.project_path}`
|
|
55783
|
+
}
|
|
55784
|
+
});
|
|
55785
|
+
} else if (pathInvalidOnMachine) {
|
|
55786
|
+
findings.push({
|
|
55787
|
+
...base,
|
|
55788
|
+
category: "null_working_dir",
|
|
55789
|
+
severity: "warn",
|
|
55790
|
+
repair_class: "blocker_invalid_path",
|
|
55791
|
+
expected_working_dir: rs.route.project_path,
|
|
55792
|
+
task_list_state: listState,
|
|
55793
|
+
detail: "working_dir is empty but the owning project path is not present on this machine; cannot safely set it here.",
|
|
55794
|
+
suggested_repair: null
|
|
55795
|
+
});
|
|
55796
|
+
}
|
|
55797
|
+
} else if (!isWorktreePath(task2.working_dir) && workingDir !== projectPath) {
|
|
55798
|
+
if (crossRepoForeign) {
|
|
55799
|
+
findings.push({
|
|
55800
|
+
...base,
|
|
55801
|
+
category: "wrong_working_dir",
|
|
55802
|
+
severity: "warn",
|
|
55803
|
+
repair_class: "blocker_cross_repo",
|
|
55804
|
+
expected_working_dir: rs.route.project_path,
|
|
55805
|
+
task_list_state: listState,
|
|
55806
|
+
detail: `working_dir (${task2.working_dir}) differs from the owning project and the title names a different repo (${crossRepoForeign}); resolve ownership first.`,
|
|
55807
|
+
suggested_repair: null
|
|
55808
|
+
});
|
|
55809
|
+
} else if (haveGoodProjectPath) {
|
|
55810
|
+
findings.push({
|
|
55811
|
+
...base,
|
|
55812
|
+
category: "wrong_working_dir",
|
|
55813
|
+
severity: "error",
|
|
55814
|
+
repair_class: "safe_auto",
|
|
55815
|
+
expected_working_dir: rs.route.project_path,
|
|
55816
|
+
task_list_state: listState,
|
|
55817
|
+
detail: `working_dir (${task2.working_dir}) does not match the owning project path; repoint it.`,
|
|
55818
|
+
suggested_repair: {
|
|
55819
|
+
field: "working_dir",
|
|
55820
|
+
from: task2.working_dir,
|
|
55821
|
+
to: rs.route.project_path,
|
|
55822
|
+
command: `todos update ${task2.id} --working-dir ${rs.route.project_path}`
|
|
55823
|
+
}
|
|
55824
|
+
});
|
|
55825
|
+
} else if (pathInvalidOnMachine) {
|
|
55826
|
+
findings.push({
|
|
55827
|
+
...base,
|
|
55828
|
+
category: "wrong_working_dir",
|
|
55829
|
+
severity: "warn",
|
|
55830
|
+
repair_class: "blocker_invalid_path",
|
|
55831
|
+
expected_working_dir: rs.route.project_path,
|
|
55832
|
+
task_list_state: listState,
|
|
55833
|
+
detail: `working_dir (${task2.working_dir}) differs from the owning project, but that project path is absent on this machine; reported, not rewritten.`,
|
|
55834
|
+
suggested_repair: null
|
|
55835
|
+
});
|
|
55836
|
+
}
|
|
55837
|
+
}
|
|
55838
|
+
}
|
|
55839
|
+
if (listState === "null") {
|
|
55840
|
+
const canonical = resolveCanonicalTaskList(project, db);
|
|
55841
|
+
if (canonical) {
|
|
55842
|
+
findings.push({
|
|
55843
|
+
...base,
|
|
55844
|
+
category: "null_task_list_id",
|
|
55845
|
+
severity: "error",
|
|
55846
|
+
repair_class: "safe_auto",
|
|
55847
|
+
expected_working_dir: rs.route.project_path,
|
|
55848
|
+
task_list_state: listState,
|
|
55849
|
+
detail: `task_list_id is null; link to the owning project's task list (${canonical.slug ?? canonical.id}) by UUID.`,
|
|
55850
|
+
suggested_repair: {
|
|
55851
|
+
field: "task_list_id",
|
|
55852
|
+
from: null,
|
|
55853
|
+
to: canonical.id,
|
|
55854
|
+
command: `todos update ${task2.id} --list ${canonical.id}`
|
|
55855
|
+
}
|
|
55856
|
+
});
|
|
55857
|
+
} else if (project) {
|
|
55858
|
+
findings.push({
|
|
55859
|
+
...base,
|
|
55860
|
+
category: "null_task_list_id",
|
|
55861
|
+
severity: "warn",
|
|
55862
|
+
repair_class: "unsupported",
|
|
55863
|
+
expected_working_dir: rs.route.project_path,
|
|
55864
|
+
task_list_state: listState,
|
|
55865
|
+
detail: `task_list_id is null and the owning project has no resolvable task list${project.task_list_id ? ` (slug ${project.task_list_id} resolves to no row)` : ""}; create the list before linking.`,
|
|
55866
|
+
suggested_repair: null
|
|
55867
|
+
});
|
|
55868
|
+
}
|
|
55869
|
+
} else if (listState === "unresolvable") {
|
|
55870
|
+
const canonical = resolveCanonicalTaskList(project, db);
|
|
55871
|
+
if (canonical && canonical.id !== task2.task_list_id) {
|
|
55872
|
+
findings.push({
|
|
55873
|
+
...base,
|
|
55874
|
+
category: "unresolvable_task_list",
|
|
55875
|
+
severity: "error",
|
|
55876
|
+
repair_class: "safe_auto",
|
|
55877
|
+
expected_working_dir: rs.route.project_path,
|
|
55878
|
+
task_list_state: listState,
|
|
55879
|
+
detail: `task_list_id (${task2.task_list_id}) resolves to no task list; relink to the owning project's list (${canonical.slug ?? canonical.id}) by UUID.`,
|
|
55880
|
+
suggested_repair: {
|
|
55881
|
+
field: "task_list_id",
|
|
55882
|
+
from: task2.task_list_id,
|
|
55883
|
+
to: canonical.id,
|
|
55884
|
+
command: `todos update ${task2.id} --list ${canonical.id}`
|
|
55885
|
+
}
|
|
55886
|
+
});
|
|
55887
|
+
} else {
|
|
55888
|
+
findings.push({
|
|
55889
|
+
...base,
|
|
55890
|
+
category: "unresolvable_task_list",
|
|
55891
|
+
severity: "warn",
|
|
55892
|
+
repair_class: "blocker_human",
|
|
55893
|
+
expected_working_dir: rs.route.project_path,
|
|
55894
|
+
task_list_state: listState,
|
|
55895
|
+
detail: `task_list_id (${task2.task_list_id}) resolves to no task list and no canonical project list is available to relink to; reported without guessing.`,
|
|
55896
|
+
suggested_repair: null
|
|
55897
|
+
});
|
|
55898
|
+
}
|
|
55899
|
+
}
|
|
55900
|
+
if (rs.gates.tag_opt_in && rs.gates.no_auto) {
|
|
55901
|
+
findings.push({
|
|
55902
|
+
...base,
|
|
55903
|
+
category: "no_auto_conflict",
|
|
55904
|
+
severity: "warn",
|
|
55905
|
+
repair_class: "blocker_human",
|
|
55906
|
+
expected_working_dir: rs.route.project_path,
|
|
55907
|
+
task_list_state: listState,
|
|
55908
|
+
detail: "Task carries both an auto:route/route:enabled tag and a no-auto deny signal; a triage decision is required (doctor does not guess).",
|
|
55909
|
+
suggested_repair: null
|
|
55910
|
+
});
|
|
55911
|
+
}
|
|
55912
|
+
if (rs.gates.tag_opt_in && !rs.eligible && rs.reasons.includes("route_not_enabled")) {
|
|
55913
|
+
findings.push({
|
|
55914
|
+
...base,
|
|
55915
|
+
category: "route_not_enabled",
|
|
55916
|
+
severity: "warn",
|
|
55917
|
+
repair_class: "blocker_human",
|
|
55918
|
+
expected_working_dir: rs.route.project_path,
|
|
55919
|
+
task_list_state: listState,
|
|
55920
|
+
detail: "Task is tag-opted-in to routing but an explicit route_enabled:false denies it; reconcile the contradiction (doctor does not override an explicit deny).",
|
|
55921
|
+
suggested_repair: null
|
|
55922
|
+
});
|
|
55923
|
+
}
|
|
55924
|
+
return { findings, eligible: rs.eligible };
|
|
55925
|
+
}
|
|
55926
|
+
function createBackup2(dbPath, generatedAt) {
|
|
55927
|
+
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
55928
|
+
return;
|
|
55929
|
+
if (!existsSync22(dbPath))
|
|
55930
|
+
return;
|
|
55931
|
+
const stamp = generatedAt.replace(/[:.]/g, "-");
|
|
55932
|
+
const backupDir = join21(dirname12(dbPath), `${basename9(dbPath)}.routing-doctor-backup-${stamp}`);
|
|
55933
|
+
const files = [];
|
|
55934
|
+
mkdirSync11(backupDir, { recursive: true });
|
|
55935
|
+
for (const source3 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
55936
|
+
if (!existsSync22(source3))
|
|
55937
|
+
continue;
|
|
55938
|
+
const target = join21(backupDir, basename9(source3));
|
|
55939
|
+
copyFileSync2(source3, target);
|
|
55940
|
+
files.push(target);
|
|
55941
|
+
}
|
|
55942
|
+
return files.length > 0 ? { path: backupDir, files } : undefined;
|
|
55943
|
+
}
|
|
55944
|
+
function routingRepairUndoCommand(repair) {
|
|
55945
|
+
if (repair.field === "working_dir") {
|
|
55946
|
+
return repair.from === null ? `todos update ${repair.task_id} --clear-working-dir` : `todos update ${repair.task_id} --working-dir ${repair.from}`;
|
|
55947
|
+
}
|
|
55948
|
+
return repair.from === null ? `todos update ${repair.task_id} --clear-list` : `todos update ${repair.task_id} --list ${repair.from}`;
|
|
55949
|
+
}
|
|
55950
|
+
function applySafeRepair(finding2, actor, generatedAt, db) {
|
|
55951
|
+
const repair = finding2.suggested_repair;
|
|
55952
|
+
const record = {
|
|
55953
|
+
task_id: finding2.task_id,
|
|
55954
|
+
task_short_id: finding2.task_short_id,
|
|
55955
|
+
category: finding2.category,
|
|
55956
|
+
field: repair.field,
|
|
55957
|
+
from: repair.from,
|
|
55958
|
+
to: repair.to,
|
|
55959
|
+
command: repair.command,
|
|
55960
|
+
applied: false
|
|
55961
|
+
};
|
|
55962
|
+
try {
|
|
55963
|
+
const current = getTask(finding2.task_id, db);
|
|
55964
|
+
if (!current)
|
|
55965
|
+
throw new Error("task not found");
|
|
55966
|
+
const patch = repair.field === "working_dir" ? { version: current.version, working_dir: repair.to } : { version: current.version, task_list_id: repair.to };
|
|
55967
|
+
updateTask(finding2.task_id, patch, db);
|
|
55968
|
+
const recheck = getTaskRouteState(finding2.task_id, db, { verifyProjectRoot: true });
|
|
55969
|
+
addComment({
|
|
55970
|
+
task_id: finding2.task_id,
|
|
55971
|
+
agent_id: actor,
|
|
55972
|
+
type: "comment",
|
|
55973
|
+
content: `[routing-doctor] repaired ${repair.field}: from=${repair.from ?? "(null)"} to=${repair.to}; ` + `source_project=${finding2.project_id ?? "(none)"}${finding2.project_name ? ` (${finding2.project_name})` : ""}` + `${finding2.project_path ? ` path=${finding2.project_path}` : ""}; command="${repair.command}"; ` + `doctor_run=${generatedAt}; route_recheck=eligible:${recheck.eligible} class:${recheck.route_class}`
|
|
55974
|
+
}, db);
|
|
55975
|
+
record.applied = true;
|
|
55976
|
+
} catch (error) {
|
|
55977
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
55978
|
+
}
|
|
55979
|
+
return record;
|
|
55980
|
+
}
|
|
55981
|
+
function runRoutingDoctor(options = {}) {
|
|
55982
|
+
const db = options.db ?? getDatabase();
|
|
55983
|
+
const dbPath = options.dbPath ?? getDatabasePath();
|
|
55984
|
+
const clock = options.now ?? now;
|
|
55985
|
+
const generatedAt = clock();
|
|
55986
|
+
const apply = options.apply === true;
|
|
55987
|
+
const statuses = options.statuses && options.statuses.length > 0 ? options.statuses : DEFAULT_STATUSES;
|
|
55988
|
+
const verifyProjectRoot = options.verifyProjectRoot !== false;
|
|
55989
|
+
const shardTotal = options.shardTotal && options.shardTotal > 1 ? Math.floor(options.shardTotal) : 1;
|
|
55990
|
+
const shardIndex = shardTotal > 1 ? Math.max(0, Math.floor(options.shardIndex ?? 0)) % shardTotal : 0;
|
|
55991
|
+
const actor = options.actor ?? "routing-doctor";
|
|
55992
|
+
const knownRepoSlugs = new Set;
|
|
55993
|
+
const projectCache = new Map;
|
|
55994
|
+
for (const project of listProjects(db)) {
|
|
55995
|
+
projectCache.set(project.id, project);
|
|
55996
|
+
knownRepoSlugs.add(repoSlugForProject(project));
|
|
55997
|
+
}
|
|
55998
|
+
const resolveProjectCached = (id) => {
|
|
55999
|
+
if (!id)
|
|
56000
|
+
return null;
|
|
56001
|
+
if (projectCache.has(id))
|
|
56002
|
+
return projectCache.get(id) ?? null;
|
|
56003
|
+
const project = getProject(id, db);
|
|
56004
|
+
projectCache.set(id, project);
|
|
56005
|
+
return project;
|
|
56006
|
+
};
|
|
56007
|
+
const filter = {
|
|
56008
|
+
status: statuses,
|
|
56009
|
+
include_archived: options.includeArchived === true
|
|
56010
|
+
};
|
|
56011
|
+
if (options.projectId)
|
|
56012
|
+
filter.project_id = options.projectId;
|
|
56013
|
+
if (options.tag)
|
|
56014
|
+
filter.tags = [options.tag];
|
|
56015
|
+
let tasks = listTasks(filter, db);
|
|
56016
|
+
if (shardTotal > 1)
|
|
56017
|
+
tasks = tasks.filter((task2) => routingShardOf(task2, shardTotal) === shardIndex);
|
|
56018
|
+
if (options.limit && options.limit > 0)
|
|
56019
|
+
tasks = tasks.slice(0, options.limit);
|
|
56020
|
+
const findings = [];
|
|
56021
|
+
let eligible = 0;
|
|
56022
|
+
for (const task2 of tasks) {
|
|
56023
|
+
const project = resolveProjectCached(task2.project_id);
|
|
56024
|
+
const evaluation = evaluateTaskRouting({ task: task2, project, db, knownRepoSlugs, verifyProjectRoot });
|
|
56025
|
+
if (evaluation.findings.length > 0)
|
|
56026
|
+
findings.push(...evaluation.findings);
|
|
56027
|
+
if (evaluation.eligible)
|
|
56028
|
+
eligible++;
|
|
56029
|
+
}
|
|
56030
|
+
const repairs = [];
|
|
56031
|
+
let backup;
|
|
56032
|
+
let undoRecordPath;
|
|
56033
|
+
if (apply) {
|
|
56034
|
+
const safeFindings = findings.filter((f) => f.repair_class === "safe_auto" && f.suggested_repair);
|
|
56035
|
+
if (safeFindings.length > 0) {
|
|
56036
|
+
backup = createBackup2(dbPath, generatedAt);
|
|
56037
|
+
for (const finding2 of safeFindings) {
|
|
56038
|
+
repairs.push(applySafeRepair(finding2, actor, generatedAt, db));
|
|
56039
|
+
}
|
|
56040
|
+
const applied = repairs.filter((r) => r.applied);
|
|
56041
|
+
if (applied.length > 0) {
|
|
56042
|
+
const undoPath = options.undoRecordPath ?? join21(process.cwd(), `todos-routing-doctor-undo-${generatedAt.replace(/[:.]/g, "-")}.json`);
|
|
56043
|
+
const undoRecord = {
|
|
56044
|
+
schema_version: TODOS_ROUTING_DOCTOR_SCHEMA_VERSION,
|
|
56045
|
+
purpose: "Undo record for routing-doctor --apply. Restore each field with the prior value below.",
|
|
56046
|
+
generated_at: generatedAt,
|
|
56047
|
+
actor,
|
|
56048
|
+
database_path: dbPath,
|
|
56049
|
+
backup: backup ?? null,
|
|
56050
|
+
repairs: applied.map((r) => ({
|
|
56051
|
+
task_id: r.task_id,
|
|
56052
|
+
field: r.field,
|
|
56053
|
+
from: r.from,
|
|
56054
|
+
to: r.to,
|
|
56055
|
+
command: r.command,
|
|
56056
|
+
undo_command: routingRepairUndoCommand(r)
|
|
56057
|
+
}))
|
|
56058
|
+
};
|
|
56059
|
+
try {
|
|
56060
|
+
writeFileSync9(undoPath, `${JSON.stringify(undoRecord, null, 2)}
|
|
56061
|
+
`);
|
|
56062
|
+
undoRecordPath = undoPath;
|
|
56063
|
+
} catch {
|
|
56064
|
+
undoRecordPath = undefined;
|
|
56065
|
+
}
|
|
56066
|
+
}
|
|
56067
|
+
}
|
|
56068
|
+
}
|
|
56069
|
+
const residualFindings = apply && repairs.some((r) => r.applied) ? runRoutingDoctor({
|
|
56070
|
+
...options,
|
|
56071
|
+
apply: false,
|
|
56072
|
+
db,
|
|
56073
|
+
dbPath,
|
|
56074
|
+
now: () => generatedAt
|
|
56075
|
+
}).findings : findings;
|
|
56076
|
+
const byCategory = {};
|
|
56077
|
+
const byRepairClass = {};
|
|
56078
|
+
for (const f of residualFindings) {
|
|
56079
|
+
bump(byCategory, f.category);
|
|
56080
|
+
bump(byRepairClass, f.repair_class);
|
|
56081
|
+
}
|
|
56082
|
+
const safeAuto = residualFindings.filter((f) => f.repair_class === "safe_auto").length;
|
|
56083
|
+
const unsupported = residualFindings.filter((f) => f.repair_class === "unsupported").length;
|
|
56084
|
+
const blockers = residualFindings.filter((f) => f.repair_class.startsWith("blocker_")).length;
|
|
56085
|
+
const repaired = repairs.filter((r) => r.applied).length;
|
|
56086
|
+
const repairFailed = repairs.filter((r) => !r.applied).length;
|
|
56087
|
+
const summary = {
|
|
56088
|
+
inspected: tasks.length,
|
|
56089
|
+
eligible,
|
|
56090
|
+
findings_total: residualFindings.length,
|
|
56091
|
+
by_category: byCategory,
|
|
56092
|
+
by_repair_class: byRepairClass,
|
|
56093
|
+
safe_auto: safeAuto,
|
|
56094
|
+
blockers,
|
|
56095
|
+
unsupported,
|
|
56096
|
+
repaired,
|
|
56097
|
+
repair_failed: repairFailed
|
|
56098
|
+
};
|
|
56099
|
+
return {
|
|
56100
|
+
schema_version: TODOS_ROUTING_DOCTOR_SCHEMA_VERSION,
|
|
56101
|
+
generated_at: generatedAt,
|
|
56102
|
+
ok: residualFindings.length === 0,
|
|
56103
|
+
dry_run: !apply,
|
|
56104
|
+
database_path: dbPath,
|
|
56105
|
+
scope: {
|
|
56106
|
+
statuses,
|
|
56107
|
+
project_id: options.projectId ?? null,
|
|
56108
|
+
tag: options.tag ?? null,
|
|
56109
|
+
shard: shardTotal > 1 ? { index: shardIndex, total: shardTotal } : null,
|
|
56110
|
+
include_archived: options.includeArchived === true,
|
|
56111
|
+
verify_project_root: verifyProjectRoot,
|
|
56112
|
+
limit: options.limit && options.limit > 0 ? options.limit : null
|
|
56113
|
+
},
|
|
56114
|
+
summary,
|
|
56115
|
+
findings: apply ? residualFindings : findings,
|
|
56116
|
+
repairs,
|
|
56117
|
+
backup,
|
|
56118
|
+
undo_record_path: undoRecordPath
|
|
56119
|
+
};
|
|
56120
|
+
}
|
|
56121
|
+
var TODOS_ROUTING_DOCTOR_SCHEMA_VERSION = "todos.routing_doctor.v1", DEFAULT_STATUSES, REPO_NAME_RE;
|
|
56122
|
+
var init_routing_doctor = __esm(() => {
|
|
56123
|
+
init_database();
|
|
56124
|
+
init_comments();
|
|
56125
|
+
init_projects();
|
|
56126
|
+
init_task_lists();
|
|
56127
|
+
init_tasks();
|
|
56128
|
+
init_task_routing();
|
|
56129
|
+
DEFAULT_STATUSES = ["pending", "in_progress"];
|
|
56130
|
+
REPO_NAME_RE = /\bopen-[a-z0-9][a-z0-9-]{1,48}\b/gi;
|
|
56131
|
+
});
|
|
56132
|
+
|
|
55594
56133
|
// src/lib/task-route-sources.ts
|
|
55595
56134
|
var exports_task_route_sources = {};
|
|
55596
56135
|
__export(exports_task_route_sources, {
|
|
@@ -55599,9 +56138,9 @@ __export(exports_task_route_sources, {
|
|
|
55599
56138
|
});
|
|
55600
56139
|
import { Database as Database3 } from "bun:sqlite";
|
|
55601
56140
|
import { createHash as createHash13 } from "crypto";
|
|
55602
|
-
import { existsSync as
|
|
55603
|
-
import { basename as
|
|
55604
|
-
function
|
|
56141
|
+
import { existsSync as existsSync23, readdirSync as readdirSync5, statSync as statSync10 } from "fs";
|
|
56142
|
+
import { basename as basename10, dirname as dirname13, join as join22, resolve as resolve21 } from "path";
|
|
56143
|
+
function normalizePath6(input) {
|
|
55605
56144
|
return resolve21(input);
|
|
55606
56145
|
}
|
|
55607
56146
|
function sourceStoreId(sourceDbPath) {
|
|
@@ -55609,14 +56148,14 @@ function sourceStoreId(sourceDbPath) {
|
|
|
55609
56148
|
return `sqlite:${digest}`;
|
|
55610
56149
|
}
|
|
55611
56150
|
function inferSourceRepoPath(sourceDbPath) {
|
|
55612
|
-
const normalized =
|
|
56151
|
+
const normalized = normalizePath6(sourceDbPath);
|
|
55613
56152
|
if (normalized.endsWith(TODO_STORE_RELATIVE_PATH)) {
|
|
55614
|
-
return
|
|
56153
|
+
return dirname13(dirname13(dirname13(normalized)));
|
|
55615
56154
|
}
|
|
55616
|
-
return
|
|
56155
|
+
return dirname13(normalized);
|
|
55617
56156
|
}
|
|
55618
56157
|
function createStoreRef(sourceDbPath) {
|
|
55619
|
-
const normalized =
|
|
56158
|
+
const normalized = normalizePath6(sourceDbPath);
|
|
55620
56159
|
return {
|
|
55621
56160
|
source_store_id: sourceStoreId(normalized),
|
|
55622
56161
|
source_repo_path: inferSourceRepoPath(normalized),
|
|
@@ -55653,7 +56192,7 @@ function storeMatchesAny(ref, patterns) {
|
|
|
55653
56192
|
if (patterns.length === 0)
|
|
55654
56193
|
return false;
|
|
55655
56194
|
const paths = [ref.source_db_path, ref.source_repo_path].filter((value) => Boolean(value));
|
|
55656
|
-
const values = paths.flatMap((value) => [value,
|
|
56195
|
+
const values = paths.flatMap((value) => [value, basename10(value)]);
|
|
55657
56196
|
return patterns.some((pattern) => values.some((value) => matchesPattern4(value, pattern)));
|
|
55658
56197
|
}
|
|
55659
56198
|
function shouldIncludeStore(ref, include, exclude) {
|
|
@@ -55661,11 +56200,11 @@ function shouldIncludeStore(ref, include, exclude) {
|
|
|
55661
56200
|
return included && !storeMatchesAny(ref, exclude);
|
|
55662
56201
|
}
|
|
55663
56202
|
function discoverStoresUnderRoot(sourceRoot) {
|
|
55664
|
-
const rootPath =
|
|
56203
|
+
const rootPath = normalizePath6(sourceRoot);
|
|
55665
56204
|
const errors2 = [];
|
|
55666
56205
|
const stores = [];
|
|
55667
|
-
if (!
|
|
55668
|
-
const ref = createStoreRef(
|
|
56206
|
+
if (!existsSync23(rootPath)) {
|
|
56207
|
+
const ref = createStoreRef(join22(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
55669
56208
|
errors2.push({
|
|
55670
56209
|
...ref,
|
|
55671
56210
|
code: "SOURCE_ROOT_MISSING",
|
|
@@ -55677,7 +56216,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
55677
56216
|
try {
|
|
55678
56217
|
rootStat = statSync10(rootPath);
|
|
55679
56218
|
} catch (error) {
|
|
55680
|
-
const ref = createStoreRef(
|
|
56219
|
+
const ref = createStoreRef(join22(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
55681
56220
|
errors2.push({
|
|
55682
56221
|
...ref,
|
|
55683
56222
|
code: "SOURCE_ROOT_UNREADABLE",
|
|
@@ -55690,8 +56229,8 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
55690
56229
|
return { stores, errors: errors2 };
|
|
55691
56230
|
}
|
|
55692
56231
|
function scanDirectory(dir, depth) {
|
|
55693
|
-
const candidate =
|
|
55694
|
-
if (
|
|
56232
|
+
const candidate = join22(dir, TODO_STORE_RELATIVE_PATH);
|
|
56233
|
+
if (existsSync23(candidate)) {
|
|
55695
56234
|
stores.push(createStoreRef(candidate));
|
|
55696
56235
|
}
|
|
55697
56236
|
if (depth >= ROOT_SCAN_MAX_DEPTH)
|
|
@@ -55711,7 +56250,7 @@ function discoverStoresUnderRoot(sourceRoot) {
|
|
|
55711
56250
|
for (const entry of entries) {
|
|
55712
56251
|
if (!entry.isDirectory() || SKIPPED_SCAN_DIRS.has(entry.name))
|
|
55713
56252
|
continue;
|
|
55714
|
-
scanDirectory(
|
|
56253
|
+
scanDirectory(join22(dir, entry.name), depth + 1);
|
|
55715
56254
|
}
|
|
55716
56255
|
}
|
|
55717
56256
|
scanDirectory(rootPath, 0);
|
|
@@ -55737,7 +56276,7 @@ function collectStoreRefs(input) {
|
|
|
55737
56276
|
};
|
|
55738
56277
|
}
|
|
55739
56278
|
function openReadonlyStore(ref) {
|
|
55740
|
-
if (!
|
|
56279
|
+
if (!existsSync23(ref.source_db_path)) {
|
|
55741
56280
|
throw Object.assign(new Error(`Store does not exist: ${ref.source_db_path}`), { code: "STORE_MISSING" });
|
|
55742
56281
|
}
|
|
55743
56282
|
return new Database3(ref.source_db_path, { readonly: true, create: false });
|
|
@@ -55846,8 +56385,8 @@ function errorCode(error) {
|
|
|
55846
56385
|
function discoverTaskRouteSources(input) {
|
|
55847
56386
|
const include = normalizePatterns(input.include);
|
|
55848
56387
|
const exclude = normalizePatterns(input.exclude);
|
|
55849
|
-
const sourceRoots = (input.sourceRoots ?? []).map(
|
|
55850
|
-
const sourceStores = (input.sourceStores ?? []).map(
|
|
56388
|
+
const sourceRoots = (input.sourceRoots ?? []).map(normalizePath6).sort();
|
|
56389
|
+
const sourceStores = (input.sourceStores ?? []).map(normalizePath6).sort();
|
|
55851
56390
|
const limit = Number.isFinite(input.limit ?? NaN) && (input.limit ?? 0) >= 0 ? Math.floor(input.limit ?? 0) : null;
|
|
55852
56391
|
const collected = collectStoreRefs(input);
|
|
55853
56392
|
const stores = [];
|
|
@@ -55910,7 +56449,7 @@ var init_task_route_sources = __esm(() => {
|
|
|
55910
56449
|
init_task_crud();
|
|
55911
56450
|
init_redaction();
|
|
55912
56451
|
init_task_routing();
|
|
55913
|
-
TODO_STORE_RELATIVE_PATH =
|
|
56452
|
+
TODO_STORE_RELATIVE_PATH = join22(".hasna", "todos", "todos.db");
|
|
55914
56453
|
SKIPPED_SCAN_DIRS = new Set([
|
|
55915
56454
|
".git",
|
|
55916
56455
|
".hg",
|
|
@@ -56407,7 +56946,7 @@ __export(exports_query_commands, {
|
|
|
56407
56946
|
registerQueryCommands: () => registerQueryCommands
|
|
56408
56947
|
});
|
|
56409
56948
|
import chalk7 from "chalk";
|
|
56410
|
-
import { readFileSync as readFileSync16, writeFileSync as
|
|
56949
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
|
|
56411
56950
|
function parseJsonObjectOption2(value, label) {
|
|
56412
56951
|
if (!value)
|
|
56413
56952
|
return;
|
|
@@ -57051,7 +57590,7 @@ No task claimed (nothing available).`));
|
|
|
57051
57590
|
console.log(lines.join(`
|
|
57052
57591
|
`));
|
|
57053
57592
|
});
|
|
57054
|
-
program2.command("doctor").description("Diagnose and optionally repair local task data issues").option("--apply", "Apply safe repairs. Defaults to dry-run.").option("--fix", "Alias for --apply").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
57593
|
+
const doctor = program2.command("doctor").description("Diagnose and optionally repair local task data issues").option("--apply", "Apply safe repairs. Defaults to dry-run.").option("--fix", "Alias for --apply").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
57055
57594
|
const globalOpts = program2.opts();
|
|
57056
57595
|
const { runTodosDoctor: runTodosDoctor2 } = await Promise.resolve().then(() => (init_doctor(), exports_doctor));
|
|
57057
57596
|
const result = runTodosDoctor2({ apply: Boolean(opts.apply || opts.fix) });
|
|
@@ -57091,6 +57630,127 @@ Repairs`));
|
|
|
57091
57630
|
console.log(chalk7[errors2 > 0 ? "red" : "yellow"](`
|
|
57092
57631
|
${errors2} error(s), ${warnings} warning(s) remain after repair.`));
|
|
57093
57632
|
});
|
|
57633
|
+
doctor.command("routing").description("Diagnose (and with --apply, safely repair) task routing-metadata drift: working_dir, task_list_id linkage, invalid paths, cross-repo intent").option("--apply", "Apply safe auto-repairs (working_dir, task_list_id UUID relink) with per-task comments, a DB backup, and an undo record. Defaults to dry-run.").option("--fix", "Alias for --apply").option("--project <id>", "Scope to a single project (id, slug, or path)").option("--tag <tag>", "Scope to tasks carrying this tag").option("--status <statuses>", "Comma-separated statuses to inspect (default: pending,in_progress)").option("--shard <index/total>", "Deterministic project-stable shard, e.g. 0/6").option("--include-archived", "Include archived tasks").option("--no-verify-project-root", "Skip machine-local project-root existence checks").option("--limit <n>", "Cap the number of tasks inspected").option("--undo-record <path>", "Where to write the undo record when --apply mutates").option("-j, --json", "Emit the machine-consumable JSON contract (todos.routing_doctor.v1)").addHelpText("after", `
|
|
57634
|
+
Exit codes (for OpenLoops / deterministic consumers):
|
|
57635
|
+
0 no findings (clean)
|
|
57636
|
+
1 routing-metadata findings present (drift detected)
|
|
57637
|
+
2 invalid invocation (bad --shard/--status/--project/--limit)
|
|
57638
|
+
|
|
57639
|
+
JSON contract: --json emits { schema_version, generated_at, ok, dry_run, scope,
|
|
57640
|
+
summary{inspected,eligible,findings_total,by_category,by_repair_class,safe_auto,
|
|
57641
|
+
blockers,unsupported,repaired,repair_failed}, findings[], repairs[] }. Each finding
|
|
57642
|
+
carries repair_class: safe_auto | blocker_human | blocker_cross_repo |
|
|
57643
|
+
blocker_invalid_path | unsupported. Only safe_auto findings are ever mutated by --apply.`).action(async (opts) => {
|
|
57644
|
+
const globalOpts = program2.opts();
|
|
57645
|
+
const parentOpts = doctor.opts();
|
|
57646
|
+
const applyRequested = Boolean(opts.apply || opts.fix || parentOpts.apply || parentOpts.fix);
|
|
57647
|
+
const { runRoutingDoctor: runRoutingDoctor2 } = await Promise.resolve().then(() => (init_routing_doctor(), exports_routing_doctor));
|
|
57648
|
+
let shardIndex;
|
|
57649
|
+
let shardTotal;
|
|
57650
|
+
if (opts.shard) {
|
|
57651
|
+
const m = /^(\d+)\/(\d+)$/.exec(String(opts.shard).trim());
|
|
57652
|
+
if (!m) {
|
|
57653
|
+
console.error(chalk7.red("--shard must be <index>/<total>, e.g. 0/6"));
|
|
57654
|
+
process.exit(2);
|
|
57655
|
+
}
|
|
57656
|
+
shardIndex = parseInt(m[1], 10);
|
|
57657
|
+
shardTotal = parseInt(m[2], 10);
|
|
57658
|
+
if (shardTotal < 1 || shardIndex >= shardTotal) {
|
|
57659
|
+
console.error(chalk7.red("--shard: index must be < total and total >= 1"));
|
|
57660
|
+
process.exit(2);
|
|
57661
|
+
}
|
|
57662
|
+
}
|
|
57663
|
+
let statuses;
|
|
57664
|
+
if (opts.status) {
|
|
57665
|
+
statuses = String(opts.status).split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
57666
|
+
const invalid = statuses.filter((s2) => !TASK_STATUSES.includes(s2));
|
|
57667
|
+
if (invalid.length > 0) {
|
|
57668
|
+
console.error(chalk7.red(`Invalid status(es): ${invalid.join(", ")}. Valid: ${TASK_STATUSES.join(", ")}`));
|
|
57669
|
+
process.exit(2);
|
|
57670
|
+
}
|
|
57671
|
+
}
|
|
57672
|
+
const projectInput = opts.project || globalOpts.project;
|
|
57673
|
+
let projectId;
|
|
57674
|
+
if (projectInput) {
|
|
57675
|
+
try {
|
|
57676
|
+
projectId = resolveExplicitProject(projectInput).id;
|
|
57677
|
+
} catch {
|
|
57678
|
+
console.error(chalk7.red(`Could not resolve project: ${projectInput}`));
|
|
57679
|
+
process.exit(2);
|
|
57680
|
+
}
|
|
57681
|
+
}
|
|
57682
|
+
let limit;
|
|
57683
|
+
if (opts.limit !== undefined) {
|
|
57684
|
+
limit = parseInt(opts.limit, 10);
|
|
57685
|
+
if (!Number.isFinite(limit) || limit < 1) {
|
|
57686
|
+
console.error(chalk7.red("--limit must be a positive integer"));
|
|
57687
|
+
process.exit(2);
|
|
57688
|
+
}
|
|
57689
|
+
}
|
|
57690
|
+
const result = runRoutingDoctor2({
|
|
57691
|
+
apply: applyRequested,
|
|
57692
|
+
statuses,
|
|
57693
|
+
projectId,
|
|
57694
|
+
tag: opts.tag,
|
|
57695
|
+
shardIndex,
|
|
57696
|
+
shardTotal,
|
|
57697
|
+
includeArchived: Boolean(opts.includeArchived),
|
|
57698
|
+
verifyProjectRoot: opts.verifyProjectRoot !== false,
|
|
57699
|
+
undoRecordPath: opts.undoRecord,
|
|
57700
|
+
limit,
|
|
57701
|
+
actor: globalOpts.agent || "routing-doctor"
|
|
57702
|
+
});
|
|
57703
|
+
if (opts.json || globalOpts.json) {
|
|
57704
|
+
output(result, true);
|
|
57705
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
57706
|
+
return;
|
|
57707
|
+
}
|
|
57708
|
+
const s = result.summary;
|
|
57709
|
+
console.log(chalk7.bold(`todos doctor routing
|
|
57710
|
+
`));
|
|
57711
|
+
console.log(` ${chalk7.dim("Mode:")} ${result.dry_run ? "dry-run" : "apply"}`);
|
|
57712
|
+
const scopeBits = [
|
|
57713
|
+
`statuses=${result.scope.statuses.join(",")}`,
|
|
57714
|
+
result.scope.project_id ? `project=${result.scope.project_id}` : null,
|
|
57715
|
+
result.scope.tag ? `tag=${result.scope.tag}` : null,
|
|
57716
|
+
result.scope.shard ? `shard=${result.scope.shard.index}/${result.scope.shard.total}` : null,
|
|
57717
|
+
result.scope.include_archived ? "archived=included" : null,
|
|
57718
|
+
result.scope.verify_project_root ? null : "verify-project-root=off"
|
|
57719
|
+
].filter(Boolean);
|
|
57720
|
+
console.log(` ${chalk7.dim("Scope:")} ${scopeBits.join(" \xB7 ")}`);
|
|
57721
|
+
console.log(` ${chalk7.dim("Tasks:")} ${s.inspected} inspected \xB7 ${s.eligible} route-eligible`);
|
|
57722
|
+
console.log(` ${chalk7.dim("Findings:")} ${s.findings_total} (${chalk7.yellow(`${s.safe_auto} safe_auto`)} \xB7 ${chalk7.red(`${s.blockers} blockers`)} \xB7 ${s.unsupported} unsupported)`);
|
|
57723
|
+
const cats = Object.entries(s.by_category).sort((a, b) => b[1] - a[1]);
|
|
57724
|
+
for (const [cat, n] of cats)
|
|
57725
|
+
console.log(` ${chalk7.dim("\xB7")} ${cat}: ${n}`);
|
|
57726
|
+
if (!result.dry_run) {
|
|
57727
|
+
console.log(` ${chalk7.dim("Repaired:")} ${s.repaired}${s.repair_failed ? chalk7.red(` (${s.repair_failed} failed)`) : ""}`);
|
|
57728
|
+
if (result.backup)
|
|
57729
|
+
console.log(` ${chalk7.dim("Backup:")} ${result.backup.path}`);
|
|
57730
|
+
if (result.undo_record_path)
|
|
57731
|
+
console.log(` ${chalk7.dim("Undo:")} ${result.undo_record_path}`);
|
|
57732
|
+
}
|
|
57733
|
+
if (result.findings.length > 0) {
|
|
57734
|
+
console.log(chalk7.bold(`
|
|
57735
|
+
Findings`));
|
|
57736
|
+
const shown = result.findings.slice(0, 50);
|
|
57737
|
+
for (const f of shown) {
|
|
57738
|
+
const id = f.task_short_id || f.task_id.slice(0, 8);
|
|
57739
|
+
const icon = f.severity === "error" ? chalk7.red("x") : chalk7.yellow("!");
|
|
57740
|
+
console.log(` ${icon} ${chalk7.bold(id)} ${f.category} [${f.repair_class}]${f.suggested_repair ? chalk7.dim(` \u2192 ${f.suggested_repair.command}`) : ""}`);
|
|
57741
|
+
console.log(` ${chalk7.dim(f.detail)}`);
|
|
57742
|
+
}
|
|
57743
|
+
if (result.findings.length > shown.length)
|
|
57744
|
+
console.log(chalk7.dim(` \u2026 +${result.findings.length - shown.length} more (use --json for the full set)`));
|
|
57745
|
+
}
|
|
57746
|
+
if (result.ok)
|
|
57747
|
+
console.log(chalk7.green(`
|
|
57748
|
+
No routing-metadata drift detected.`));
|
|
57749
|
+
else
|
|
57750
|
+
console.log(chalk7.yellow(`
|
|
57751
|
+
${s.findings_total} finding(s). ${result.dry_run ? "Re-run with --apply to fix the safe_auto set; blockers need a human/owning repo." : "Remaining findings need a human/owning repo."}`));
|
|
57752
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
57753
|
+
});
|
|
57094
57754
|
program2.command("health").description("Check todos system health \u2014 database, config, connectivity").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
57095
57755
|
const globalOpts = program2.opts();
|
|
57096
57756
|
const checks = [];
|
|
@@ -57098,9 +57758,9 @@ Repairs`));
|
|
|
57098
57758
|
const db = getDatabase();
|
|
57099
57759
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
57100
57760
|
const { statSync: statSync11 } = await import("fs");
|
|
57101
|
-
const { join:
|
|
57761
|
+
const { join: join23 } = await import("path");
|
|
57102
57762
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
57103
|
-
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] ||
|
|
57763
|
+
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join23(home, ".hasna", "todos", "todos.db");
|
|
57104
57764
|
let size = "unknown";
|
|
57105
57765
|
try {
|
|
57106
57766
|
size = `${(statSync11(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -57767,7 +58427,7 @@ Repairs`));
|
|
|
57767
58427
|
const bundle = exportHandoffBundle(opts.export, db);
|
|
57768
58428
|
const json2 = JSON.stringify(bundle, null, 2);
|
|
57769
58429
|
if (opts.output) {
|
|
57770
|
-
|
|
58430
|
+
writeFileSync10(opts.output, `${json2}
|
|
57771
58431
|
`);
|
|
57772
58432
|
if (opts.json || globalOpts.json) {
|
|
57773
58433
|
console.log(JSON.stringify({ path: opts.output, handoff_id: bundle.handoff.id }));
|
|
@@ -57998,7 +58658,7 @@ Repairs`));
|
|
|
57998
58658
|
});
|
|
57999
58659
|
const content = format === "json" ? JSON.stringify(document, null, 2) : renderReleaseNotesMarkdown2(document);
|
|
58000
58660
|
if (opts.out) {
|
|
58001
|
-
|
|
58661
|
+
writeFileSync10(opts.out, content);
|
|
58002
58662
|
if (format !== "json")
|
|
58003
58663
|
console.log(chalk7.green(`Wrote release notes to ${opts.out}`));
|
|
58004
58664
|
return;
|
|
@@ -58113,7 +58773,7 @@ Repairs`));
|
|
|
58113
58773
|
redact: Boolean(opts.redact)
|
|
58114
58774
|
});
|
|
58115
58775
|
if (opts.out) {
|
|
58116
|
-
|
|
58776
|
+
writeFileSync10(opts.out, exported.content);
|
|
58117
58777
|
if (!(opts.json || globalOpts.json))
|
|
58118
58778
|
console.log(chalk7.green(`Wrote ${exported.events.length} events to ${opts.out}`));
|
|
58119
58779
|
}
|
|
@@ -58268,7 +58928,7 @@ Repairs`));
|
|
|
58268
58928
|
const bundle = exportTaskBoardBundle(boardId);
|
|
58269
58929
|
const json2 = JSON.stringify(bundle, null, 2);
|
|
58270
58930
|
if (opts.out) {
|
|
58271
|
-
|
|
58931
|
+
writeFileSync10(opts.out, json2);
|
|
58272
58932
|
if (!(opts.json || program2.opts().json))
|
|
58273
58933
|
console.log(chalk7.green(`Wrote ${bundle.boards.length} board(s) to ${opts.out}`));
|
|
58274
58934
|
}
|
|
@@ -58941,6 +59601,7 @@ var init_query_commands = __esm(() => {
|
|
|
58941
59601
|
init_workflow_states();
|
|
58942
59602
|
init_local_reports();
|
|
58943
59603
|
init_helpers();
|
|
59604
|
+
init_types();
|
|
58944
59605
|
});
|
|
58945
59606
|
|
|
58946
59607
|
// src/cli/commands/mcp-hooks-commands.ts
|
|
@@ -58950,21 +59611,21 @@ __export(exports_mcp_hooks_commands, {
|
|
|
58950
59611
|
});
|
|
58951
59612
|
import chalk8 from "chalk";
|
|
58952
59613
|
import { execSync as execSync3 } from "child_process";
|
|
58953
|
-
import { existsSync as
|
|
58954
|
-
import { dirname as
|
|
59614
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync11, mkdirSync as mkdirSync12, chmodSync as chmodSync2 } from "fs";
|
|
59615
|
+
import { dirname as dirname14, join as join23 } from "path";
|
|
58955
59616
|
function getMcpBinaryPath() {
|
|
58956
59617
|
try {
|
|
58957
59618
|
const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
58958
59619
|
if (p)
|
|
58959
59620
|
return p;
|
|
58960
59621
|
} catch {}
|
|
58961
|
-
const bunBin =
|
|
58962
|
-
if (
|
|
59622
|
+
const bunBin = join23(HOME2, ".bun", "bin", "todos-mcp");
|
|
59623
|
+
if (existsSync24(bunBin))
|
|
58963
59624
|
return bunBin;
|
|
58964
59625
|
return "todos-mcp";
|
|
58965
59626
|
}
|
|
58966
59627
|
function readJsonFile2(path) {
|
|
58967
|
-
if (!
|
|
59628
|
+
if (!existsSync24(path))
|
|
58968
59629
|
return {};
|
|
58969
59630
|
try {
|
|
58970
59631
|
return JSON.parse(readFileSync17(path, "utf-8"));
|
|
@@ -58973,22 +59634,22 @@ function readJsonFile2(path) {
|
|
|
58973
59634
|
}
|
|
58974
59635
|
}
|
|
58975
59636
|
function writeJsonFile2(path, data) {
|
|
58976
|
-
const dir =
|
|
58977
|
-
if (!
|
|
58978
|
-
|
|
58979
|
-
|
|
59637
|
+
const dir = dirname14(path);
|
|
59638
|
+
if (!existsSync24(dir))
|
|
59639
|
+
mkdirSync12(dir, { recursive: true });
|
|
59640
|
+
writeFileSync11(path, JSON.stringify(data, null, 2) + `
|
|
58980
59641
|
`);
|
|
58981
59642
|
}
|
|
58982
59643
|
function readTomlFile(path) {
|
|
58983
|
-
if (!
|
|
59644
|
+
if (!existsSync24(path))
|
|
58984
59645
|
return "";
|
|
58985
59646
|
return readFileSync17(path, "utf-8");
|
|
58986
59647
|
}
|
|
58987
59648
|
function writeTomlFile(path, content) {
|
|
58988
|
-
const dir =
|
|
58989
|
-
if (!
|
|
58990
|
-
|
|
58991
|
-
|
|
59649
|
+
const dir = dirname14(path);
|
|
59650
|
+
if (!existsSync24(dir))
|
|
59651
|
+
mkdirSync12(dir, { recursive: true });
|
|
59652
|
+
writeFileSync11(path, content);
|
|
58992
59653
|
}
|
|
58993
59654
|
function removeTomlBlock(content, blockName) {
|
|
58994
59655
|
const lines = content.split(`
|
|
@@ -59052,7 +59713,7 @@ function unregisterClaude(_global) {
|
|
|
59052
59713
|
}
|
|
59053
59714
|
}
|
|
59054
59715
|
function registerCodex(binPath) {
|
|
59055
|
-
const configPath =
|
|
59716
|
+
const configPath = join23(HOME2, ".codex", "config.toml");
|
|
59056
59717
|
let content = readTomlFile(configPath);
|
|
59057
59718
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
59058
59719
|
const block = `
|
|
@@ -59066,7 +59727,7 @@ args = ["--stdio"]
|
|
|
59066
59727
|
console.log(chalk8.green(`Codex CLI: registered in ${configPath}`));
|
|
59067
59728
|
}
|
|
59068
59729
|
function unregisterCodex() {
|
|
59069
|
-
const configPath =
|
|
59730
|
+
const configPath = join23(HOME2, ".codex", "config.toml");
|
|
59070
59731
|
let content = readTomlFile(configPath);
|
|
59071
59732
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
59072
59733
|
console.log(chalk8.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -59078,7 +59739,7 @@ function unregisterCodex() {
|
|
|
59078
59739
|
console.log(chalk8.green(`Codex CLI: unregistered from ${configPath}`));
|
|
59079
59740
|
}
|
|
59080
59741
|
function registerGemini(binPath) {
|
|
59081
|
-
const configPath =
|
|
59742
|
+
const configPath = join23(HOME2, ".gemini", "settings.json");
|
|
59082
59743
|
const config = readJsonFile2(configPath);
|
|
59083
59744
|
if (!config["mcpServers"]) {
|
|
59084
59745
|
config["mcpServers"] = {};
|
|
@@ -59092,7 +59753,7 @@ function registerGemini(binPath) {
|
|
|
59092
59753
|
console.log(chalk8.green(`Gemini CLI: registered in ${configPath}`));
|
|
59093
59754
|
}
|
|
59094
59755
|
function unregisterGemini() {
|
|
59095
|
-
const configPath =
|
|
59756
|
+
const configPath = join23(HOME2, ".gemini", "settings.json");
|
|
59096
59757
|
const config = readJsonFile2(configPath);
|
|
59097
59758
|
const servers = config["mcpServers"];
|
|
59098
59759
|
if (!servers || !("todos" in servers)) {
|
|
@@ -59149,9 +59810,9 @@ function registerMcpHooksCommands(program2) {
|
|
|
59149
59810
|
if (p)
|
|
59150
59811
|
todosBin = p;
|
|
59151
59812
|
} catch {}
|
|
59152
|
-
const hooksDir =
|
|
59153
|
-
if (!
|
|
59154
|
-
|
|
59813
|
+
const hooksDir = join23(process.cwd(), ".claude", "hooks");
|
|
59814
|
+
if (!existsSync24(hooksDir))
|
|
59815
|
+
mkdirSync12(hooksDir, { recursive: true });
|
|
59155
59816
|
const hookScript = `#!/usr/bin/env bash
|
|
59156
59817
|
# Auto-generated by: todos hooks install
|
|
59157
59818
|
# Syncs todos with Claude Code task list on tool use events.
|
|
@@ -59175,11 +59836,11 @@ esac
|
|
|
59175
59836
|
|
|
59176
59837
|
exit 0
|
|
59177
59838
|
`;
|
|
59178
|
-
const hookPath =
|
|
59179
|
-
|
|
59839
|
+
const hookPath = join23(hooksDir, "todos-sync.sh");
|
|
59840
|
+
writeFileSync11(hookPath, hookScript);
|
|
59180
59841
|
execSync3(`chmod +x "${hookPath}"`);
|
|
59181
59842
|
console.log(chalk8.green(`Hook script created: ${hookPath}`));
|
|
59182
|
-
const settingsPath =
|
|
59843
|
+
const settingsPath = join23(process.cwd(), ".claude", "settings.json");
|
|
59183
59844
|
const settings = readJsonFile2(settingsPath);
|
|
59184
59845
|
if (!settings["hooks"]) {
|
|
59185
59846
|
settings["hooks"] = {};
|
|
@@ -60052,18 +60713,18 @@ Artifacts:`));
|
|
|
60052
60713
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
60053
60714
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
60054
60715
|
const marker = "# todos-auto-link";
|
|
60055
|
-
if (
|
|
60716
|
+
if (existsSync24(hookPath)) {
|
|
60056
60717
|
const existing = readFileSync17(hookPath, "utf-8");
|
|
60057
60718
|
if (existing.includes(marker)) {
|
|
60058
60719
|
console.log(chalk8.yellow("Hook already installed."));
|
|
60059
60720
|
return;
|
|
60060
60721
|
}
|
|
60061
|
-
|
|
60722
|
+
writeFileSync11(hookPath, existing + `
|
|
60062
60723
|
${marker}
|
|
60063
60724
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
60064
60725
|
`);
|
|
60065
60726
|
} else {
|
|
60066
|
-
|
|
60727
|
+
writeFileSync11(hookPath, `#!/usr/bin/env bash
|
|
60067
60728
|
${marker}
|
|
60068
60729
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
60069
60730
|
`);
|
|
@@ -60080,7 +60741,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
60080
60741
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
60081
60742
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
60082
60743
|
const marker = "# todos-auto-link";
|
|
60083
|
-
if (!
|
|
60744
|
+
if (!existsSync24(hookPath)) {
|
|
60084
60745
|
console.log(chalk8.dim("No post-commit hook found."));
|
|
60085
60746
|
return;
|
|
60086
60747
|
}
|
|
@@ -60095,7 +60756,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
60095
60756
|
if (cleaned === "#!/usr/bin/env bash" || cleaned === "") {
|
|
60096
60757
|
(await import("fs")).unlinkSync(hookPath);
|
|
60097
60758
|
} else {
|
|
60098
|
-
|
|
60759
|
+
writeFileSync11(hookPath, cleaned + `
|
|
60099
60760
|
`);
|
|
60100
60761
|
}
|
|
60101
60762
|
console.log(chalk8.green("Post-commit hook removed."));
|
|
@@ -60264,9 +60925,9 @@ __export(exports_machines, {
|
|
|
60264
60925
|
});
|
|
60265
60926
|
import chalk10 from "chalk";
|
|
60266
60927
|
import { execSync as execSync4 } from "child_process";
|
|
60267
|
-
import { readFileSync as readFileSync18, unlinkSync as unlinkSync2, writeFileSync as
|
|
60928
|
+
import { readFileSync as readFileSync18, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "fs";
|
|
60268
60929
|
import { tmpdir as tmpdir4 } from "os";
|
|
60269
|
-
import { join as
|
|
60930
|
+
import { join as join24 } from "path";
|
|
60270
60931
|
function getOrCreateLocalMachineName() {
|
|
60271
60932
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
60272
60933
|
}
|
|
@@ -60304,7 +60965,7 @@ function remoteTempPath(sshAddress) {
|
|
|
60304
60965
|
}
|
|
60305
60966
|
function readRemoteBridgeBundle(sshAddress) {
|
|
60306
60967
|
const remotePath = remoteTempPath(sshAddress);
|
|
60307
|
-
const localPath =
|
|
60968
|
+
const localPath = join24(tmpdir4(), `todos-bridge-pull-${uuid()}.json`);
|
|
60308
60969
|
try {
|
|
60309
60970
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
60310
60971
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
@@ -60319,8 +60980,8 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
60319
60980
|
}
|
|
60320
60981
|
}
|
|
60321
60982
|
function writeLocalBridgeBundle() {
|
|
60322
|
-
const localPath =
|
|
60323
|
-
|
|
60983
|
+
const localPath = join24(tmpdir4(), `todos-bridge-push-${uuid()}.json`);
|
|
60984
|
+
writeFileSync12(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
|
|
60324
60985
|
return localPath;
|
|
60325
60986
|
}
|
|
60326
60987
|
function pushLocalBridgeBundle(sshAddress, dryRun) {
|
|
@@ -64843,8 +65504,8 @@ __export(exports_sdk_integration_fixtures, {
|
|
|
64843
65504
|
TODOS_SDK_INTEGRATION_FIXTURE_SCHEMA_VERSION: () => TODOS_SDK_INTEGRATION_FIXTURE_SCHEMA_VERSION,
|
|
64844
65505
|
TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT: () => TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT
|
|
64845
65506
|
});
|
|
64846
|
-
import { mkdirSync as
|
|
64847
|
-
import { join as
|
|
65507
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "fs";
|
|
65508
|
+
import { join as join25 } from "path";
|
|
64848
65509
|
function source5(version) {
|
|
64849
65510
|
return {
|
|
64850
65511
|
packageName: "@hasna/todos",
|
|
@@ -64940,7 +65601,7 @@ function createSdkIntegrationFixturePack(options = {}) {
|
|
|
64940
65601
|
};
|
|
64941
65602
|
}
|
|
64942
65603
|
function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
64943
|
-
|
|
65604
|
+
mkdirSync13(directory, { recursive: true });
|
|
64944
65605
|
const pack = createSdkIntegrationFixturePack(options);
|
|
64945
65606
|
const bundle = getOnboardingFixtureBundle("agent-project-demo");
|
|
64946
65607
|
const files = [
|
|
@@ -64951,8 +65612,8 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
|
64951
65612
|
];
|
|
64952
65613
|
const written = [];
|
|
64953
65614
|
for (const [name, payload] of files) {
|
|
64954
|
-
const file =
|
|
64955
|
-
|
|
65615
|
+
const file = join25(directory, name);
|
|
65616
|
+
writeFileSync13(file, `${JSON.stringify(payload, null, 2)}
|
|
64956
65617
|
`, "utf-8");
|
|
64957
65618
|
written.push(file);
|
|
64958
65619
|
}
|
|
@@ -65216,7 +65877,7 @@ var exports_roadmap_commands = {};
|
|
|
65216
65877
|
__export(exports_roadmap_commands, {
|
|
65217
65878
|
registerRoadmapCommands: () => registerRoadmapCommands
|
|
65218
65879
|
});
|
|
65219
|
-
import { readFileSync as readFileSync19, writeFileSync as
|
|
65880
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync14 } from "fs";
|
|
65220
65881
|
import chalk21 from "chalk";
|
|
65221
65882
|
function splitList3(value) {
|
|
65222
65883
|
return value?.split(";").flatMap((part) => part.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
@@ -65427,7 +66088,7 @@ function registerRoadmapCommands(program2) {
|
|
|
65427
66088
|
const { exportRoadmapBundle: exportRoadmapBundle2, renderRoadmapMarkdown: renderRoadmapMarkdown2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
65428
66089
|
const content = opts.format === "markdown" ? renderRoadmapMarkdown2(roadmap) : JSON.stringify(exportRoadmapBundle2(roadmap), null, 2);
|
|
65429
66090
|
if (opts.out) {
|
|
65430
|
-
|
|
66091
|
+
writeFileSync14(opts.out, content);
|
|
65431
66092
|
if (!globalOpts.json)
|
|
65432
66093
|
console.log(chalk21.green(`Wrote roadmap export to ${opts.out}`));
|
|
65433
66094
|
}
|