@hasna/todos 0.11.70 → 0.11.72
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/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/index.js +722 -102
- package/dist/contracts.js +159 -10
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/plans.d.ts +8 -0
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +985 -439
- package/dist/lib/json-schemas.d.ts.map +1 -1
- package/dist/lib/local-bridge.d.ts.map +1 -1
- package/dist/lib/onboarding-fixtures.d.ts.map +1 -1
- package/dist/lib/plan-artifacts.d.ts +3 -0
- package/dist/lib/plan-artifacts.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/lib/task-route-contract.d.ts +2 -1
- package/dist/lib/task-route-contract.d.ts.map +1 -1
- package/dist/lib/task-route-sources.d.ts +68 -0
- package/dist/lib/task-route-sources.d.ts.map +1 -0
- package/dist/lib/task-routing.d.ts.map +1 -1
- package/dist/mcp/index.js +162 -10
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/registry.js +159 -10
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +164 -12
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/storage.js +177 -12
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1198,6 +1198,11 @@ var init_migrations = __esm(() => {
|
|
|
1198
1198
|
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
|
|
1199
1199
|
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
|
|
1200
1200
|
INSERT OR IGNORE INTO _migrations (id) VALUES (63);
|
|
1201
|
+
`,
|
|
1202
|
+
`
|
|
1203
|
+
ALTER TABLE plans ADD COLUMN slug TEXT;
|
|
1204
|
+
CREATE INDEX IF NOT EXISTS idx_plans_slug ON plans(slug);
|
|
1205
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (64);
|
|
1201
1206
|
`
|
|
1202
1207
|
];
|
|
1203
1208
|
});
|
|
@@ -1221,6 +1226,29 @@ function runMigrations(db) {
|
|
|
1221
1226
|
}
|
|
1222
1227
|
ensureSchema(db);
|
|
1223
1228
|
}
|
|
1229
|
+
function planSlugBase(value) {
|
|
1230
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "plan";
|
|
1231
|
+
}
|
|
1232
|
+
function backfillPlanSlugs(db) {
|
|
1233
|
+
try {
|
|
1234
|
+
const rows = db.query("SELECT id, project_id, name, slug FROM plans ORDER BY created_at ASC, id ASC").all();
|
|
1235
|
+
const used = new Set;
|
|
1236
|
+
for (const row of rows) {
|
|
1237
|
+
const scope = row.project_id ?? "__global__";
|
|
1238
|
+
const base = planSlugBase(row.slug || row.name);
|
|
1239
|
+
let candidate = base;
|
|
1240
|
+
let suffix = 2;
|
|
1241
|
+
while (used.has(`${scope}:${candidate}`)) {
|
|
1242
|
+
candidate = `${base}-${suffix}`;
|
|
1243
|
+
suffix += 1;
|
|
1244
|
+
}
|
|
1245
|
+
used.add(`${scope}:${candidate}`);
|
|
1246
|
+
if (row.slug !== candidate) {
|
|
1247
|
+
db.run("UPDATE plans SET slug = ? WHERE id = ?", [candidate, row.id]);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
} catch {}
|
|
1251
|
+
}
|
|
1224
1252
|
function ensureSchema(db) {
|
|
1225
1253
|
const ensureColumn = (table, column, type) => {
|
|
1226
1254
|
try {
|
|
@@ -1270,7 +1298,8 @@ function ensureSchema(db) {
|
|
|
1270
1298
|
)`);
|
|
1271
1299
|
ensureTable("plans", `
|
|
1272
1300
|
CREATE TABLE plans (
|
|
1273
|
-
id TEXT PRIMARY KEY,
|
|
1301
|
+
id TEXT PRIMARY KEY, slug TEXT,
|
|
1302
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
1274
1303
|
task_list_id TEXT, agent_id TEXT,
|
|
1275
1304
|
name TEXT NOT NULL, description TEXT,
|
|
1276
1305
|
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
|
|
@@ -1785,8 +1814,10 @@ function ensureSchema(db) {
|
|
|
1785
1814
|
ensureColumn("agents", "org_id", "TEXT");
|
|
1786
1815
|
ensureColumn("agents", "capabilities", "TEXT DEFAULT '[]'");
|
|
1787
1816
|
ensureColumn("projects", "org_id", "TEXT");
|
|
1817
|
+
ensureColumn("plans", "slug", "TEXT");
|
|
1788
1818
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
1789
1819
|
ensureColumn("plans", "agent_id", "TEXT");
|
|
1820
|
+
backfillPlanSlugs(db);
|
|
1790
1821
|
ensureColumn("task_templates", "variables", "TEXT DEFAULT '[]'");
|
|
1791
1822
|
ensureColumn("task_templates", "version", "INTEGER NOT NULL DEFAULT 1");
|
|
1792
1823
|
ensureColumn("template_tasks", "condition", "TEXT");
|
|
@@ -1901,6 +1932,8 @@ function ensureSchema(db) {
|
|
|
1901
1932
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name)");
|
|
1902
1933
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
|
|
1903
1934
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
|
|
1935
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_slug ON plans(slug)");
|
|
1936
|
+
ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_scope_slug ON plans(COALESCE(project_id, ''), slug) WHERE slug IS NOT NULL");
|
|
1904
1937
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
|
|
1905
1938
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
|
|
1906
1939
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
|
|
@@ -2734,6 +2767,9 @@ function clearExpiredLocks(db) {
|
|
|
2734
2767
|
const cutoff = lockExpiryCutoff();
|
|
2735
2768
|
db.run("UPDATE tasks SET locked_by = NULL, locked_at = NULL WHERE locked_at IS NOT NULL AND locked_at < ?", [cutoff]);
|
|
2736
2769
|
}
|
|
2770
|
+
function slugifyRef(value) {
|
|
2771
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2772
|
+
}
|
|
2737
2773
|
function resolvePartialId(db, table, partialId) {
|
|
2738
2774
|
if (!ALLOWED_TABLES.has(table)) {
|
|
2739
2775
|
throw new Error(`Invalid table name: ${table}`);
|
|
@@ -2760,6 +2796,16 @@ function resolvePartialId(db, table, partialId) {
|
|
|
2760
2796
|
if (slugRow)
|
|
2761
2797
|
return slugRow.id;
|
|
2762
2798
|
}
|
|
2799
|
+
if (table === "plans") {
|
|
2800
|
+
const slug = slugifyRef(partialId);
|
|
2801
|
+
if (slug) {
|
|
2802
|
+
const slugRows = db.query("SELECT id FROM plans WHERE slug = ?").all(slug);
|
|
2803
|
+
if (slugRows.length === 1)
|
|
2804
|
+
return slugRows[0].id;
|
|
2805
|
+
if (slugRows.length > 1)
|
|
2806
|
+
return null;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2763
2809
|
if (table === "projects") {
|
|
2764
2810
|
const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
2765
2811
|
if (nameRow)
|
|
@@ -7985,8 +8031,6 @@ function routeEnabledForTask(task, taskList) {
|
|
|
7985
8031
|
const explicit = booleanField(task.metadata.route_enabled);
|
|
7986
8032
|
if (explicit !== undefined)
|
|
7987
8033
|
return explicit;
|
|
7988
|
-
if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
|
|
7989
|
-
return true;
|
|
7990
8034
|
const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
|
|
7991
8035
|
if (taskListDefault !== undefined)
|
|
7992
8036
|
return taskListDefault;
|
|
@@ -8008,8 +8052,26 @@ function workflowPointersFromMetadata(metadata) {
|
|
|
8008
8052
|
function compactWorkflowPointers(pointers) {
|
|
8009
8053
|
return Object.fromEntries(Object.entries(pointers).filter(([, value]) => typeof value === "string" && value.length > 0));
|
|
8010
8054
|
}
|
|
8011
|
-
function
|
|
8012
|
-
|
|
8055
|
+
function metadataStringField(record, keys) {
|
|
8056
|
+
if (!record)
|
|
8057
|
+
return;
|
|
8058
|
+
for (const key of keys) {
|
|
8059
|
+
const value = record[key];
|
|
8060
|
+
if (typeof value === "string" && value.trim())
|
|
8061
|
+
return value.trim();
|
|
8062
|
+
}
|
|
8063
|
+
return;
|
|
8064
|
+
}
|
|
8065
|
+
function projectKindFromMetadata(...records) {
|
|
8066
|
+
for (const record of records) {
|
|
8067
|
+
const value = metadataStringField(record ?? undefined, ["project_kind", "projectKind", "source_kind", "sourceKind"]);
|
|
8068
|
+
if (value)
|
|
8069
|
+
return value;
|
|
8070
|
+
}
|
|
8071
|
+
return null;
|
|
8072
|
+
}
|
|
8073
|
+
function classifyProjectKind(_path, metadata) {
|
|
8074
|
+
return projectKindFromMetadata(metadata);
|
|
8013
8075
|
}
|
|
8014
8076
|
function isWorktreePath(path) {
|
|
8015
8077
|
return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
|
|
@@ -8082,7 +8144,6 @@ function taskEventMetadata(task) {
|
|
|
8082
8144
|
metadata.project_canonical_path = projectPath;
|
|
8083
8145
|
}
|
|
8084
8146
|
if (projectPath) {
|
|
8085
|
-
metadata.project_kind = classifyProjectKind(projectPath);
|
|
8086
8147
|
metadata.project_is_worktree = isWorktreePath(projectPath);
|
|
8087
8148
|
metadata.working_dir = task.working_dir ?? projectPath;
|
|
8088
8149
|
}
|
|
@@ -8094,6 +8155,10 @@ function taskEventMetadata(task) {
|
|
|
8094
8155
|
metadata.task_list_project_id = taskList.project_id;
|
|
8095
8156
|
metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
|
|
8096
8157
|
}
|
|
8158
|
+
const projectKind = projectKindFromMetadata(task.metadata, taskList?.metadata);
|
|
8159
|
+
if (projectKind) {
|
|
8160
|
+
metadata.project_kind = classifyProjectKind(projectPath ?? "", { project_kind: projectKind });
|
|
8161
|
+
}
|
|
8097
8162
|
const routeEnabled = routeEnabledForTask(task, taskList);
|
|
8098
8163
|
if (routeEnabled !== undefined) {
|
|
8099
8164
|
metadata.route_enabled = routeEnabled;
|
|
@@ -10736,15 +10801,59 @@ init_database();
|
|
|
10736
10801
|
// src/db/plans.ts
|
|
10737
10802
|
init_types();
|
|
10738
10803
|
init_database();
|
|
10804
|
+
function planSlugBase2(value) {
|
|
10805
|
+
return slugify(value) || "plan";
|
|
10806
|
+
}
|
|
10807
|
+
function normalizePlanSlug(value) {
|
|
10808
|
+
const slug = slugify(value);
|
|
10809
|
+
if (!slug)
|
|
10810
|
+
throw new Error("Invalid plan slug");
|
|
10811
|
+
return slug;
|
|
10812
|
+
}
|
|
10813
|
+
function plansBySlug(slug, db, projectId) {
|
|
10814
|
+
if (projectId !== undefined) {
|
|
10815
|
+
if (projectId === null) {
|
|
10816
|
+
return db.query("SELECT * FROM plans WHERE slug = ? AND project_id IS NULL ORDER BY created_at ASC, id ASC").all(slug);
|
|
10817
|
+
}
|
|
10818
|
+
return db.query("SELECT * FROM plans WHERE slug = ? AND project_id = ? ORDER BY created_at ASC, id ASC").all(slug, projectId);
|
|
10819
|
+
}
|
|
10820
|
+
return db.query("SELECT * FROM plans WHERE slug = ? ORDER BY created_at ASC, id ASC").all(slug);
|
|
10821
|
+
}
|
|
10822
|
+
function planSlugExists(slug, projectId, db, excludeId) {
|
|
10823
|
+
const rows = plansBySlug(slug, db, projectId);
|
|
10824
|
+
return rows.some((plan) => plan.id !== excludeId);
|
|
10825
|
+
}
|
|
10826
|
+
function nextPlanSlug(base, projectId, db, excludeId) {
|
|
10827
|
+
let candidate = base;
|
|
10828
|
+
let suffix = 2;
|
|
10829
|
+
while (planSlugExists(candidate, projectId, db, excludeId)) {
|
|
10830
|
+
candidate = `${base}-${suffix}`;
|
|
10831
|
+
suffix += 1;
|
|
10832
|
+
}
|
|
10833
|
+
return candidate;
|
|
10834
|
+
}
|
|
10835
|
+
function resolveCreateSlug(input, projectId, db) {
|
|
10836
|
+
if (input.slug !== undefined) {
|
|
10837
|
+
const slug = normalizePlanSlug(input.slug);
|
|
10838
|
+
if (planSlugExists(slug, projectId, db)) {
|
|
10839
|
+
throw new Error(`Plan slug already exists in this scope: ${slug}`);
|
|
10840
|
+
}
|
|
10841
|
+
return slug;
|
|
10842
|
+
}
|
|
10843
|
+
return nextPlanSlug(planSlugBase2(input.name), projectId, db);
|
|
10844
|
+
}
|
|
10739
10845
|
function createPlan(input, db) {
|
|
10740
10846
|
const d = db || getDatabase();
|
|
10741
10847
|
const id = uuid();
|
|
10742
10848
|
const timestamp = now();
|
|
10849
|
+
const projectId = input.project_id || null;
|
|
10850
|
+
const slug = resolveCreateSlug(input, projectId, d);
|
|
10743
10851
|
const machineId = currentStorageMachineId(d);
|
|
10744
|
-
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
10745
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10852
|
+
d.run(`INSERT INTO plans (id, slug, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
10853
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10746
10854
|
id,
|
|
10747
|
-
|
|
10855
|
+
slug,
|
|
10856
|
+
projectId,
|
|
10748
10857
|
input.task_list_id || null,
|
|
10749
10858
|
input.agent_id || null,
|
|
10750
10859
|
input.name,
|
|
@@ -10779,6 +10888,14 @@ function updatePlan(id, input, db) {
|
|
|
10779
10888
|
sets.push("name = ?");
|
|
10780
10889
|
params.push(input.name);
|
|
10781
10890
|
}
|
|
10891
|
+
if (input.slug !== undefined) {
|
|
10892
|
+
const slug = normalizePlanSlug(input.slug);
|
|
10893
|
+
if (planSlugExists(slug, plan.project_id, d, id)) {
|
|
10894
|
+
throw new Error(`Plan slug already exists in this scope: ${slug}`);
|
|
10895
|
+
}
|
|
10896
|
+
sets.push("slug = ?");
|
|
10897
|
+
params.push(slug);
|
|
10898
|
+
}
|
|
10782
10899
|
if (input.description !== undefined) {
|
|
10783
10900
|
sets.push("description = ?");
|
|
10784
10901
|
params.push(input.description);
|
|
@@ -12632,7 +12749,7 @@ var dataKeys = [
|
|
|
12632
12749
|
var insertColumns = {
|
|
12633
12750
|
projects: ["id", "name", "path", "description", "task_list_id", "task_prefix", "task_counter", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
12634
12751
|
task_lists: ["id", "project_id", "slug", "name", "description", "metadata", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
12635
|
-
plans: ["id", "project_id", "task_list_id", "agent_id", "name", "description", "status", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
12752
|
+
plans: ["id", "slug", "project_id", "task_list_id", "agent_id", "name", "description", "status", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
12636
12753
|
tasks: [
|
|
12637
12754
|
"id",
|
|
12638
12755
|
"short_id",
|
|
@@ -12924,6 +13041,36 @@ function prepareValue(column, value) {
|
|
|
12924
13041
|
return JSON.stringify(value ?? (column === "tags" || column === "files_changed" ? [] : {}));
|
|
12925
13042
|
return value === undefined ? null : value;
|
|
12926
13043
|
}
|
|
13044
|
+
function slugifyPlanValue(value) {
|
|
13045
|
+
return typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") : "";
|
|
13046
|
+
}
|
|
13047
|
+
function planSlugBase3(plan) {
|
|
13048
|
+
return slugifyPlanValue(plan.slug) || slugifyPlanValue(plan.name) || "plan";
|
|
13049
|
+
}
|
|
13050
|
+
function planSlugScope(projectId) {
|
|
13051
|
+
return typeof projectId === "string" && projectId ? projectId : "__global__";
|
|
13052
|
+
}
|
|
13053
|
+
function planSlugKey(projectId, slug) {
|
|
13054
|
+
return `${planSlugScope(projectId)}:${slug}`;
|
|
13055
|
+
}
|
|
13056
|
+
function normalizeBridgePlanSlugs(plans, db) {
|
|
13057
|
+
const existingRows = db.query("SELECT id, project_id, slug FROM plans WHERE slug IS NOT NULL").all();
|
|
13058
|
+
const existingIds = new Set(existingRows.map((row) => row.id));
|
|
13059
|
+
const used = new Set(existingRows.filter((row) => row.slug).map((row) => planSlugKey(row.project_id, row.slug)));
|
|
13060
|
+
return plans.map((plan) => {
|
|
13061
|
+
if (existingIds.has(plan.id))
|
|
13062
|
+
return plan;
|
|
13063
|
+
const base = planSlugBase3(plan);
|
|
13064
|
+
let candidate = base;
|
|
13065
|
+
let suffix = 2;
|
|
13066
|
+
while (used.has(planSlugKey(plan.project_id, candidate))) {
|
|
13067
|
+
candidate = `${base}-${suffix}`;
|
|
13068
|
+
suffix += 1;
|
|
13069
|
+
}
|
|
13070
|
+
used.add(planSlugKey(plan.project_id, candidate));
|
|
13071
|
+
return { ...plan, slug: candidate };
|
|
13072
|
+
});
|
|
13073
|
+
}
|
|
12927
13074
|
function insertRecord(db, tableKey, row) {
|
|
12928
13075
|
const table = tableByKey[tableKey];
|
|
12929
13076
|
const columns = insertColumns[tableKey];
|
|
@@ -13060,6 +13207,7 @@ function importLocalBridgeBundle(bundle, options = {}, db) {
|
|
|
13060
13207
|
const conflictStrategy = options.conflictStrategy ?? "skip";
|
|
13061
13208
|
const data = {
|
|
13062
13209
|
...bundle.data,
|
|
13210
|
+
plans: normalizeBridgePlanSlugs(bundle.data.plans, d),
|
|
13063
13211
|
tasks: sortedTasks(bundle.data.tasks),
|
|
13064
13212
|
saved_views: bundle.data.saved_views ?? [],
|
|
13065
13213
|
task_boards: bundle.data.task_boards ?? [],
|
|
@@ -13253,6 +13401,7 @@ function createAgentProjectDemoBundle() {
|
|
|
13253
13401
|
});
|
|
13254
13402
|
data.plans.push({
|
|
13255
13403
|
id: ids.plan,
|
|
13404
|
+
slug: "ship-local-demo-workflow",
|
|
13256
13405
|
project_id: ids.project,
|
|
13257
13406
|
task_list_id: ids.list,
|
|
13258
13407
|
agent_id: "demo-agent",
|
|
@@ -23380,9 +23529,17 @@ async function updateProject2(id, input, store) {
|
|
|
23380
23529
|
}
|
|
23381
23530
|
async function createPlan2(input, store, context) {
|
|
23382
23531
|
const timestamp2 = new Date().toISOString();
|
|
23532
|
+
const projectId = input.project_id ?? context?.projectId ?? null;
|
|
23533
|
+
const slug = await resolvePostgresPlanSlug({
|
|
23534
|
+
name: input.name,
|
|
23535
|
+
slug: input.slug,
|
|
23536
|
+
projectId,
|
|
23537
|
+
store
|
|
23538
|
+
});
|
|
23383
23539
|
return store.upsert("plans", {
|
|
23384
23540
|
id: randomUUID3(),
|
|
23385
|
-
|
|
23541
|
+
slug,
|
|
23542
|
+
project_id: projectId,
|
|
23386
23543
|
task_list_id: input.task_list_id ?? context?.taskListId ?? null,
|
|
23387
23544
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
23388
23545
|
name: input.name,
|
|
@@ -23396,7 +23553,17 @@ async function createPlan2(input, store, context) {
|
|
|
23396
23553
|
}
|
|
23397
23554
|
async function updatePlan2(id, input, store) {
|
|
23398
23555
|
const plan = await requireRecord("plans", id, store);
|
|
23399
|
-
|
|
23556
|
+
const patch = definedPatch(input);
|
|
23557
|
+
if (input.slug !== undefined) {
|
|
23558
|
+
patch.slug = await resolvePostgresPlanSlug({
|
|
23559
|
+
name: plan.name,
|
|
23560
|
+
slug: input.slug,
|
|
23561
|
+
projectId: plan.project_id,
|
|
23562
|
+
store,
|
|
23563
|
+
excludeId: id
|
|
23564
|
+
});
|
|
23565
|
+
}
|
|
23566
|
+
return store.upsert("plans", { ...plan, ...patch, updated_at: new Date().toISOString() });
|
|
23400
23567
|
}
|
|
23401
23568
|
async function registerAgent2(input, store, context) {
|
|
23402
23569
|
const existing = (await store.list("agents")).find((agent2) => agent2.name === input.name && agent2.status !== "archived");
|
|
@@ -23649,8 +23816,38 @@ function matchesOne(value, expected) {
|
|
|
23649
23816
|
function priorityRank(priority) {
|
|
23650
23817
|
return { critical: 0, high: 1, medium: 2, low: 3 }[priority];
|
|
23651
23818
|
}
|
|
23819
|
+
function slugifyRaw(value) {
|
|
23820
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
23821
|
+
}
|
|
23652
23822
|
function slugify2(value) {
|
|
23653
|
-
return value
|
|
23823
|
+
return slugifyRaw(value) || "todos";
|
|
23824
|
+
}
|
|
23825
|
+
function normalizePlanSlug2(value) {
|
|
23826
|
+
const slug = slugifyRaw(value);
|
|
23827
|
+
if (!slug)
|
|
23828
|
+
throw new Error("Invalid plan slug");
|
|
23829
|
+
return slug;
|
|
23830
|
+
}
|
|
23831
|
+
function planSlugBase4(value) {
|
|
23832
|
+
return slugifyRaw(value) || "plan";
|
|
23833
|
+
}
|
|
23834
|
+
async function resolvePostgresPlanSlug(options) {
|
|
23835
|
+
const plans = await options.store.list("plans");
|
|
23836
|
+
const used = new Set(plans.filter((plan) => plan.project_id === options.projectId && plan.id !== options.excludeId && plan.slug).map((plan) => plan.slug));
|
|
23837
|
+
if (options.slug !== undefined) {
|
|
23838
|
+
const slug = normalizePlanSlug2(options.slug);
|
|
23839
|
+
if (used.has(slug))
|
|
23840
|
+
throw new Error(`Plan slug already exists in this scope: ${slug}`);
|
|
23841
|
+
return slug;
|
|
23842
|
+
}
|
|
23843
|
+
const base = planSlugBase4(options.name);
|
|
23844
|
+
let candidate = base;
|
|
23845
|
+
let suffix = 2;
|
|
23846
|
+
while (used.has(candidate)) {
|
|
23847
|
+
candidate = `${base}-${suffix}`;
|
|
23848
|
+
suffix += 1;
|
|
23849
|
+
}
|
|
23850
|
+
return candidate;
|
|
23654
23851
|
}
|
|
23655
23852
|
function definedPatch(value) {
|
|
23656
23853
|
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined));
|
|
@@ -24826,6 +25023,496 @@ function clean2(value) {
|
|
|
24826
25023
|
const trimmed = value?.trim();
|
|
24827
25024
|
return trimmed ? trimmed : undefined;
|
|
24828
25025
|
}
|
|
25026
|
+
// src/lib/task-route-sources.ts
|
|
25027
|
+
init_database();
|
|
25028
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
25029
|
+
import { createHash as createHash10 } from "crypto";
|
|
25030
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
25031
|
+
import { basename as basename2, dirname as dirname7, join as join10, resolve as resolve10 } from "path";
|
|
25032
|
+
init_redaction();
|
|
25033
|
+
|
|
25034
|
+
// src/lib/task-routing.ts
|
|
25035
|
+
init_database();
|
|
25036
|
+
function machineLocalPath(project, db) {
|
|
25037
|
+
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
25038
|
+
if (!machineId)
|
|
25039
|
+
return null;
|
|
25040
|
+
try {
|
|
25041
|
+
const row = db.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
25042
|
+
return row?.path ?? null;
|
|
25043
|
+
} catch {
|
|
25044
|
+
return null;
|
|
25045
|
+
}
|
|
25046
|
+
}
|
|
25047
|
+
function resolveProject(task2, db) {
|
|
25048
|
+
const project = task2.project_id ? getProject(task2.project_id, db) : null;
|
|
25049
|
+
const projectPath = project ? machineLocalPath(project, db) ?? project.path : task2.working_dir;
|
|
25050
|
+
return { project, projectPath: projectPath ?? null };
|
|
25051
|
+
}
|
|
25052
|
+
function resolveTaskList(task2, project, db) {
|
|
25053
|
+
if (task2.task_list_id) {
|
|
25054
|
+
return getTaskList(task2.task_list_id, db) ?? (project ? getTaskListBySlug(task2.task_list_id, project.id, db) : null);
|
|
25055
|
+
}
|
|
25056
|
+
if (project?.task_list_id) {
|
|
25057
|
+
return getTaskListBySlug(project.task_list_id, project.id, db);
|
|
25058
|
+
}
|
|
25059
|
+
return null;
|
|
25060
|
+
}
|
|
25061
|
+
function isTerminal2(status) {
|
|
25062
|
+
return status === "completed" || status === "cancelled" || status === "failed";
|
|
25063
|
+
}
|
|
25064
|
+
function routeConcurrencyKey(task2, project, taskList, projectPath) {
|
|
25065
|
+
if (project?.id)
|
|
25066
|
+
return `project:${project.id}`;
|
|
25067
|
+
if (taskList?.id)
|
|
25068
|
+
return `task-list:${taskList.id}`;
|
|
25069
|
+
if (projectPath)
|
|
25070
|
+
return `path:${projectPath}`;
|
|
25071
|
+
return `task:${task2.id}`;
|
|
25072
|
+
}
|
|
25073
|
+
function getTaskRouteState(taskOrId, db) {
|
|
25074
|
+
const d = db || getDatabase();
|
|
25075
|
+
const task2 = typeof taskOrId === "string" ? getTask(taskOrId, d) : taskOrId;
|
|
25076
|
+
if (!task2)
|
|
25077
|
+
throw new Error(`Task not found: ${taskOrId}`);
|
|
25078
|
+
const { project, projectPath } = resolveProject(task2, d);
|
|
25079
|
+
const taskList = resolveTaskList(task2, project, d);
|
|
25080
|
+
const automation = routingAutomationMetadata(task2, taskList) ?? {};
|
|
25081
|
+
const routeEnabled = routeEnabledForTask(task2, taskList) === true;
|
|
25082
|
+
const tagOptIn = task2.tags.includes("auto:route") || task2.tags.includes("route:enabled");
|
|
25083
|
+
const projectKind = projectKindFromMetadata(task2.metadata, taskList?.metadata);
|
|
25084
|
+
const locked = Boolean(task2.locked_by && !isLockExpired(task2.locked_at));
|
|
25085
|
+
const blockers = getBlockingDeps(task2.id, d);
|
|
25086
|
+
const blocked = blockers.length > 0;
|
|
25087
|
+
const terminal = isTerminal2(task2.status);
|
|
25088
|
+
const requiresApproval = automation.requires_approval === true || task2.requires_approval === true;
|
|
25089
|
+
const approvalRequired = automation.approval_required === true;
|
|
25090
|
+
const approved = Boolean(task2.approved_by);
|
|
25091
|
+
const gates = {
|
|
25092
|
+
route_enabled: routeEnabled,
|
|
25093
|
+
tag_opt_in: tagOptIn,
|
|
25094
|
+
no_auto: automation.no_auto === true,
|
|
25095
|
+
manual: automation.manual === true,
|
|
25096
|
+
manual_required: automation.manual_required === true,
|
|
25097
|
+
requires_approval: requiresApproval,
|
|
25098
|
+
approval_required: approvalRequired,
|
|
25099
|
+
approved,
|
|
25100
|
+
locked,
|
|
25101
|
+
blocked,
|
|
25102
|
+
terminal
|
|
25103
|
+
};
|
|
25104
|
+
const reasons = [];
|
|
25105
|
+
if (task2.status !== "pending")
|
|
25106
|
+
reasons.push("task_not_pending");
|
|
25107
|
+
if (terminal)
|
|
25108
|
+
reasons.push("task_terminal");
|
|
25109
|
+
if (!routeEnabled)
|
|
25110
|
+
reasons.push("route_not_enabled");
|
|
25111
|
+
if (locked)
|
|
25112
|
+
reasons.push("task_locked");
|
|
25113
|
+
if (blocked)
|
|
25114
|
+
reasons.push("task_blocked");
|
|
25115
|
+
if (gates.no_auto)
|
|
25116
|
+
reasons.push("no_auto");
|
|
25117
|
+
if (gates.manual)
|
|
25118
|
+
reasons.push("manual");
|
|
25119
|
+
if (gates.manual_required)
|
|
25120
|
+
reasons.push("manual_required");
|
|
25121
|
+
if (requiresApproval && !approved)
|
|
25122
|
+
reasons.push("requires_approval");
|
|
25123
|
+
if (approvalRequired && !approved)
|
|
25124
|
+
reasons.push("approval_required");
|
|
25125
|
+
if (automation.allowed === false)
|
|
25126
|
+
reasons.push("automation_disallowed");
|
|
25127
|
+
return {
|
|
25128
|
+
schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
25129
|
+
task_id: task2.id,
|
|
25130
|
+
task_short_id: task2.short_id,
|
|
25131
|
+
status: task2.status,
|
|
25132
|
+
eligible: reasons.length === 0,
|
|
25133
|
+
reasons,
|
|
25134
|
+
blockers: blockers.map((blocker) => ({
|
|
25135
|
+
id: blocker.id,
|
|
25136
|
+
short_id: blocker.short_id,
|
|
25137
|
+
title: blocker.title,
|
|
25138
|
+
status: blocker.status
|
|
25139
|
+
})),
|
|
25140
|
+
gates,
|
|
25141
|
+
automation: Object.keys(automation).length > 0 ? automation : null,
|
|
25142
|
+
route: {
|
|
25143
|
+
project_id: project?.id ?? task2.project_id,
|
|
25144
|
+
project_path: projectPath,
|
|
25145
|
+
working_dir: task2.working_dir ?? projectPath,
|
|
25146
|
+
project_kind: projectKind,
|
|
25147
|
+
task_list_id: taskList?.id ?? task2.task_list_id,
|
|
25148
|
+
task_list_slug: taskList?.slug ?? null,
|
|
25149
|
+
task_list_name: taskList?.name ?? null,
|
|
25150
|
+
concurrency_key: routeConcurrencyKey(task2, project, taskList, projectPath)
|
|
25151
|
+
},
|
|
25152
|
+
pointers: workflowPointersFromMetadata(task2.metadata)
|
|
25153
|
+
};
|
|
25154
|
+
}
|
|
25155
|
+
function setTaskWorkflowPointers(taskId, input, db) {
|
|
25156
|
+
const d = db || getDatabase();
|
|
25157
|
+
const task2 = getTask(taskId, d);
|
|
25158
|
+
if (!task2)
|
|
25159
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
25160
|
+
const previous = workflowPointersFromMetadata(task2.metadata);
|
|
25161
|
+
const next = compactWorkflowPointers({
|
|
25162
|
+
current_workflow_invocation_id: pointerPatch(previous.current_workflow_invocation_id, input, "current_workflow_invocation_id"),
|
|
25163
|
+
current_run_id: pointerPatch(previous.current_run_id, input, "current_run_id"),
|
|
25164
|
+
latest_manifest_path: pointerPatch(previous.latest_manifest_path, input, "latest_manifest_path"),
|
|
25165
|
+
latest_evaluation_path: pointerPatch(previous.latest_evaluation_path, input, "latest_evaluation_path"),
|
|
25166
|
+
workflow_state: pointerPatch(previous.workflow_state, input, "workflow_state")
|
|
25167
|
+
});
|
|
25168
|
+
const timestamp2 = now();
|
|
25169
|
+
const {
|
|
25170
|
+
current_workflow_invocation_id,
|
|
25171
|
+
current_run_id,
|
|
25172
|
+
latest_manifest_path,
|
|
25173
|
+
latest_evaluation_path,
|
|
25174
|
+
workflow_state,
|
|
25175
|
+
workflow_invocation,
|
|
25176
|
+
...baseMetadata
|
|
25177
|
+
} = task2.metadata;
|
|
25178
|
+
const metadata = {
|
|
25179
|
+
...baseMetadata,
|
|
25180
|
+
...next,
|
|
25181
|
+
workflow_invocation: {
|
|
25182
|
+
schema_version: TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
|
|
25183
|
+
...next,
|
|
25184
|
+
updated_at: timestamp2,
|
|
25185
|
+
updated_by: input.actor ?? null
|
|
25186
|
+
}
|
|
25187
|
+
};
|
|
25188
|
+
return updateTask(task2.id, { version: task2.version, metadata }, d);
|
|
25189
|
+
}
|
|
25190
|
+
function pointerPatch(previous, input, key) {
|
|
25191
|
+
if (!Object.prototype.hasOwnProperty.call(input, key))
|
|
25192
|
+
return previous;
|
|
25193
|
+
const value = input[key];
|
|
25194
|
+
if (value === undefined)
|
|
25195
|
+
return previous;
|
|
25196
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
25197
|
+
}
|
|
25198
|
+
|
|
25199
|
+
// src/lib/task-route-sources.ts
|
|
25200
|
+
var TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION = "todos.task_route_sources.v1";
|
|
25201
|
+
var TODO_STORE_RELATIVE_PATH = join10(".hasna", "todos", "todos.db");
|
|
25202
|
+
var ROOT_SCAN_MAX_DEPTH = 5;
|
|
25203
|
+
var SKIPPED_SCAN_DIRS = new Set([
|
|
25204
|
+
".git",
|
|
25205
|
+
".hg",
|
|
25206
|
+
".svn",
|
|
25207
|
+
"node_modules",
|
|
25208
|
+
"dist",
|
|
25209
|
+
"build",
|
|
25210
|
+
".next",
|
|
25211
|
+
".turbo",
|
|
25212
|
+
".cache"
|
|
25213
|
+
]);
|
|
25214
|
+
function normalizePath3(input) {
|
|
25215
|
+
return resolve10(input);
|
|
25216
|
+
}
|
|
25217
|
+
function sourceStoreId(sourceDbPath) {
|
|
25218
|
+
const digest = createHash10("sha256").update(sourceDbPath).digest("hex").slice(0, 16);
|
|
25219
|
+
return `sqlite:${digest}`;
|
|
25220
|
+
}
|
|
25221
|
+
function inferSourceRepoPath(sourceDbPath) {
|
|
25222
|
+
const normalized = normalizePath3(sourceDbPath);
|
|
25223
|
+
if (normalized.endsWith(TODO_STORE_RELATIVE_PATH)) {
|
|
25224
|
+
return dirname7(dirname7(dirname7(normalized)));
|
|
25225
|
+
}
|
|
25226
|
+
return dirname7(normalized);
|
|
25227
|
+
}
|
|
25228
|
+
function createStoreRef(sourceDbPath) {
|
|
25229
|
+
const normalized = normalizePath3(sourceDbPath);
|
|
25230
|
+
return {
|
|
25231
|
+
source_store_id: sourceStoreId(normalized),
|
|
25232
|
+
source_repo_path: inferSourceRepoPath(normalized),
|
|
25233
|
+
source_db_path: normalized
|
|
25234
|
+
};
|
|
25235
|
+
}
|
|
25236
|
+
function normalizePatterns(patterns) {
|
|
25237
|
+
return (patterns ?? []).map((pattern) => pattern.trim()).filter(Boolean);
|
|
25238
|
+
}
|
|
25239
|
+
function escapeRegExp(value) {
|
|
25240
|
+
return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
25241
|
+
}
|
|
25242
|
+
function globPatternToRegExp(pattern) {
|
|
25243
|
+
let source9 = "";
|
|
25244
|
+
for (const char of pattern) {
|
|
25245
|
+
if (char === "*")
|
|
25246
|
+
source9 += ".*";
|
|
25247
|
+
else if (char === "?")
|
|
25248
|
+
source9 += ".";
|
|
25249
|
+
else
|
|
25250
|
+
source9 += escapeRegExp(char);
|
|
25251
|
+
}
|
|
25252
|
+
return new RegExp(`^${source9}$`);
|
|
25253
|
+
}
|
|
25254
|
+
function matchesPattern3(value, pattern) {
|
|
25255
|
+
const normalizedValue = value.replace(/\\/g, "/");
|
|
25256
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
25257
|
+
if (normalizedPattern.includes("*") || normalizedPattern.includes("?")) {
|
|
25258
|
+
return globPatternToRegExp(normalizedPattern).test(normalizedValue);
|
|
25259
|
+
}
|
|
25260
|
+
return normalizedValue.includes(normalizedPattern);
|
|
25261
|
+
}
|
|
25262
|
+
function storeMatchesAny(ref, patterns) {
|
|
25263
|
+
if (patterns.length === 0)
|
|
25264
|
+
return false;
|
|
25265
|
+
const paths = [ref.source_db_path, ref.source_repo_path].filter((value) => Boolean(value));
|
|
25266
|
+
const values = paths.flatMap((value) => [value, basename2(value)]);
|
|
25267
|
+
return patterns.some((pattern) => values.some((value) => matchesPattern3(value, pattern)));
|
|
25268
|
+
}
|
|
25269
|
+
function shouldIncludeStore(ref, include, exclude) {
|
|
25270
|
+
const included = include.length === 0 || storeMatchesAny(ref, include);
|
|
25271
|
+
return included && !storeMatchesAny(ref, exclude);
|
|
25272
|
+
}
|
|
25273
|
+
function discoverStoresUnderRoot(sourceRoot) {
|
|
25274
|
+
const rootPath = normalizePath3(sourceRoot);
|
|
25275
|
+
const errors = [];
|
|
25276
|
+
const stores = [];
|
|
25277
|
+
if (!existsSync9(rootPath)) {
|
|
25278
|
+
const ref = createStoreRef(join10(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
25279
|
+
errors.push({
|
|
25280
|
+
...ref,
|
|
25281
|
+
code: "SOURCE_ROOT_MISSING",
|
|
25282
|
+
message: `Source root does not exist: ${rootPath}`
|
|
25283
|
+
});
|
|
25284
|
+
return { stores, errors };
|
|
25285
|
+
}
|
|
25286
|
+
let rootStat;
|
|
25287
|
+
try {
|
|
25288
|
+
rootStat = statSync3(rootPath);
|
|
25289
|
+
} catch (error) {
|
|
25290
|
+
const ref = createStoreRef(join10(rootPath, TODO_STORE_RELATIVE_PATH));
|
|
25291
|
+
errors.push({
|
|
25292
|
+
...ref,
|
|
25293
|
+
code: "SOURCE_ROOT_UNREADABLE",
|
|
25294
|
+
message: error instanceof Error ? error.message : `Unable to read source root: ${rootPath}`
|
|
25295
|
+
});
|
|
25296
|
+
return { stores, errors };
|
|
25297
|
+
}
|
|
25298
|
+
if (rootStat.isFile()) {
|
|
25299
|
+
stores.push(createStoreRef(rootPath));
|
|
25300
|
+
return { stores, errors };
|
|
25301
|
+
}
|
|
25302
|
+
function scanDirectory(dir, depth) {
|
|
25303
|
+
const candidate = join10(dir, TODO_STORE_RELATIVE_PATH);
|
|
25304
|
+
if (existsSync9(candidate)) {
|
|
25305
|
+
stores.push(createStoreRef(candidate));
|
|
25306
|
+
}
|
|
25307
|
+
if (depth >= ROOT_SCAN_MAX_DEPTH)
|
|
25308
|
+
return;
|
|
25309
|
+
let entries;
|
|
25310
|
+
try {
|
|
25311
|
+
entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
25312
|
+
} catch (error) {
|
|
25313
|
+
const ref = createStoreRef(candidate);
|
|
25314
|
+
errors.push({
|
|
25315
|
+
...ref,
|
|
25316
|
+
code: "SOURCE_ROOT_UNREADABLE",
|
|
25317
|
+
message: error instanceof Error ? error.message : `Unable to read source root: ${dir}`
|
|
25318
|
+
});
|
|
25319
|
+
return;
|
|
25320
|
+
}
|
|
25321
|
+
for (const entry2 of entries) {
|
|
25322
|
+
if (!entry2.isDirectory() || SKIPPED_SCAN_DIRS.has(entry2.name))
|
|
25323
|
+
continue;
|
|
25324
|
+
scanDirectory(join10(dir, entry2.name), depth + 1);
|
|
25325
|
+
}
|
|
25326
|
+
}
|
|
25327
|
+
scanDirectory(rootPath, 0);
|
|
25328
|
+
return { stores, errors };
|
|
25329
|
+
}
|
|
25330
|
+
function collectStoreRefs(input) {
|
|
25331
|
+
const byPath = new Map;
|
|
25332
|
+
const errors = [];
|
|
25333
|
+
for (const storePath of input.sourceStores ?? []) {
|
|
25334
|
+
const ref = createStoreRef(storePath);
|
|
25335
|
+
byPath.set(ref.source_db_path, ref);
|
|
25336
|
+
}
|
|
25337
|
+
for (const sourceRoot of input.sourceRoots ?? []) {
|
|
25338
|
+
const discovered = discoverStoresUnderRoot(sourceRoot);
|
|
25339
|
+
for (const ref of discovered.stores) {
|
|
25340
|
+
byPath.set(ref.source_db_path, ref);
|
|
25341
|
+
}
|
|
25342
|
+
errors.push(...discovered.errors);
|
|
25343
|
+
}
|
|
25344
|
+
return {
|
|
25345
|
+
stores: [...byPath.values()].sort((a, b) => a.source_db_path.localeCompare(b.source_db_path)),
|
|
25346
|
+
errors: errors.sort((a, b) => a.source_db_path.localeCompare(b.source_db_path))
|
|
25347
|
+
};
|
|
25348
|
+
}
|
|
25349
|
+
function openReadonlyStore(ref) {
|
|
25350
|
+
if (!existsSync9(ref.source_db_path)) {
|
|
25351
|
+
throw Object.assign(new Error(`Store does not exist: ${ref.source_db_path}`), { code: "STORE_MISSING" });
|
|
25352
|
+
}
|
|
25353
|
+
return new Database3(ref.source_db_path, { readonly: true, create: false });
|
|
25354
|
+
}
|
|
25355
|
+
function hasTable2(db, tableName) {
|
|
25356
|
+
const row = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(tableName);
|
|
25357
|
+
return Boolean(row);
|
|
25358
|
+
}
|
|
25359
|
+
function tableColumns2(db, tableName) {
|
|
25360
|
+
const rows = db.query(`PRAGMA table_info(${tableName})`).all();
|
|
25361
|
+
return new Set(rows.map((row) => row.name));
|
|
25362
|
+
}
|
|
25363
|
+
function listPendingTasksReadonly(db) {
|
|
25364
|
+
if (!hasTable2(db, "tasks")) {
|
|
25365
|
+
throw Object.assign(new Error("Store does not contain a tasks table"), { code: "STORE_INVALID" });
|
|
25366
|
+
}
|
|
25367
|
+
const columns = tableColumns2(db, "tasks");
|
|
25368
|
+
const conditions = ["status = 'pending'"];
|
|
25369
|
+
if (columns.has("archived_at"))
|
|
25370
|
+
conditions.push("archived_at IS NULL");
|
|
25371
|
+
const rows = db.query(`SELECT * FROM tasks WHERE ${conditions.join(" AND ")}
|
|
25372
|
+
ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END, created_at DESC`).all();
|
|
25373
|
+
return rows.map(rowToTask);
|
|
25374
|
+
}
|
|
25375
|
+
function isReadyTask(task2, db) {
|
|
25376
|
+
if (task2.locked_by && !isLockExpired(task2.locked_at))
|
|
25377
|
+
return false;
|
|
25378
|
+
return getBlockingDeps(task2.id, db).length === 0;
|
|
25379
|
+
}
|
|
25380
|
+
function metadataFingerprint(metadata) {
|
|
25381
|
+
const value = metadata.fingerprint;
|
|
25382
|
+
if (typeof value === "string" && value.trim())
|
|
25383
|
+
return value;
|
|
25384
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
25385
|
+
return String(value);
|
|
25386
|
+
return null;
|
|
25387
|
+
}
|
|
25388
|
+
function boundedMetadataValue(value, depth = 0) {
|
|
25389
|
+
if (depth > 6)
|
|
25390
|
+
return "[TRUNCATED]";
|
|
25391
|
+
if (typeof value === "string") {
|
|
25392
|
+
return value.length > 2000 ? `${value.slice(0, 2000)}[TRUNCATED]` : value;
|
|
25393
|
+
}
|
|
25394
|
+
if (Array.isArray(value)) {
|
|
25395
|
+
return value.slice(0, 50).map((item) => boundedMetadataValue(item, depth + 1));
|
|
25396
|
+
}
|
|
25397
|
+
if (value && typeof value === "object") {
|
|
25398
|
+
const result = {};
|
|
25399
|
+
for (const [key, child] of Object.entries(value).slice(0, 80)) {
|
|
25400
|
+
const normalized = key.toLowerCase();
|
|
25401
|
+
if (normalized === "comment" || normalized === "comments" || normalized === "task_comments") {
|
|
25402
|
+
result[key] = "[REDACTED_COMMENT]";
|
|
25403
|
+
continue;
|
|
25404
|
+
}
|
|
25405
|
+
result[key] = boundedMetadataValue(child, depth + 1);
|
|
25406
|
+
}
|
|
25407
|
+
return result;
|
|
25408
|
+
}
|
|
25409
|
+
return value;
|
|
25410
|
+
}
|
|
25411
|
+
function discoveryMetadata(metadata) {
|
|
25412
|
+
return redactValue(boundedMetadataValue(metadata));
|
|
25413
|
+
}
|
|
25414
|
+
function sourceCandidate(ref, task2, db) {
|
|
25415
|
+
const routeState = getTaskRouteState(task2, db);
|
|
25416
|
+
const autoRoute = task2.tags.includes("auto:route") || task2.tags.includes("route:enabled");
|
|
25417
|
+
return {
|
|
25418
|
+
source_store_id: ref.source_store_id,
|
|
25419
|
+
source_repo_path: ref.source_repo_path,
|
|
25420
|
+
source_db_path: ref.source_db_path,
|
|
25421
|
+
source_task_key: `${ref.source_store_id}:${task2.id}`,
|
|
25422
|
+
source_selected_by_input: true,
|
|
25423
|
+
task_id: task2.id,
|
|
25424
|
+
task_short_id: task2.short_id,
|
|
25425
|
+
title: task2.title,
|
|
25426
|
+
status: task2.status,
|
|
25427
|
+
priority: task2.priority,
|
|
25428
|
+
project_path: routeState.route.project_path ?? task2.working_dir ?? ref.source_repo_path,
|
|
25429
|
+
task_version: task2.version,
|
|
25430
|
+
task_updated_at: task2.updated_at,
|
|
25431
|
+
task_fingerprint: metadataFingerprint(task2.metadata),
|
|
25432
|
+
tags: task2.tags,
|
|
25433
|
+
task_intent: {
|
|
25434
|
+
auto_route: autoRoute
|
|
25435
|
+
},
|
|
25436
|
+
metadata: discoveryMetadata(task2.metadata),
|
|
25437
|
+
route_state: routeState
|
|
25438
|
+
};
|
|
25439
|
+
}
|
|
25440
|
+
function discoveryError(ref, code, error) {
|
|
25441
|
+
return {
|
|
25442
|
+
...ref,
|
|
25443
|
+
code,
|
|
25444
|
+
message: error instanceof Error ? error.message : String(error)
|
|
25445
|
+
};
|
|
25446
|
+
}
|
|
25447
|
+
function errorCode(error) {
|
|
25448
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "STORE_MISSING") {
|
|
25449
|
+
return "STORE_MISSING";
|
|
25450
|
+
}
|
|
25451
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "STORE_INVALID") {
|
|
25452
|
+
return "STORE_INVALID";
|
|
25453
|
+
}
|
|
25454
|
+
return "STORE_UNREADABLE";
|
|
25455
|
+
}
|
|
25456
|
+
function discoverTaskRouteSources(input) {
|
|
25457
|
+
const include = normalizePatterns(input.include);
|
|
25458
|
+
const exclude = normalizePatterns(input.exclude);
|
|
25459
|
+
const sourceRoots = (input.sourceRoots ?? []).map(normalizePath3).sort();
|
|
25460
|
+
const sourceStores = (input.sourceStores ?? []).map(normalizePath3).sort();
|
|
25461
|
+
const limit = Number.isFinite(input.limit ?? NaN) && (input.limit ?? 0) >= 0 ? Math.floor(input.limit ?? 0) : null;
|
|
25462
|
+
const collected = collectStoreRefs(input);
|
|
25463
|
+
const stores = [];
|
|
25464
|
+
const errors = [...collected.errors];
|
|
25465
|
+
const candidates = [];
|
|
25466
|
+
let totalCandidateCount = 0;
|
|
25467
|
+
for (const ref of collected.stores) {
|
|
25468
|
+
if (!shouldIncludeStore(ref, include, exclude))
|
|
25469
|
+
continue;
|
|
25470
|
+
const storeErrors = [];
|
|
25471
|
+
let db = null;
|
|
25472
|
+
try {
|
|
25473
|
+
db = openReadonlyStore(ref);
|
|
25474
|
+
const readyTasks = listPendingTasksReadonly(db).filter((task2) => isReadyTask(task2, db));
|
|
25475
|
+
totalCandidateCount += readyTasks.length;
|
|
25476
|
+
const remaining = limit === null ? readyTasks.length : Math.max(0, limit - candidates.length);
|
|
25477
|
+
const selectedTasks = limit === null ? readyTasks : readyTasks.slice(0, remaining);
|
|
25478
|
+
candidates.push(...selectedTasks.map((task2) => sourceCandidate(ref, task2, db)));
|
|
25479
|
+
stores.push({
|
|
25480
|
+
...ref,
|
|
25481
|
+
status: "ok",
|
|
25482
|
+
candidate_count: readyTasks.length,
|
|
25483
|
+
returned_candidate_count: selectedTasks.length,
|
|
25484
|
+
errors: []
|
|
25485
|
+
});
|
|
25486
|
+
} catch (error) {
|
|
25487
|
+
const storeError = discoveryError(ref, errorCode(error), error);
|
|
25488
|
+
storeErrors.push(storeError);
|
|
25489
|
+
errors.push(storeError);
|
|
25490
|
+
stores.push({
|
|
25491
|
+
...ref,
|
|
25492
|
+
status: storeError.code === "STORE_MISSING" ? "missing" : "error",
|
|
25493
|
+
candidate_count: 0,
|
|
25494
|
+
returned_candidate_count: 0,
|
|
25495
|
+
errors: storeErrors
|
|
25496
|
+
});
|
|
25497
|
+
} finally {
|
|
25498
|
+
db?.close();
|
|
25499
|
+
}
|
|
25500
|
+
}
|
|
25501
|
+
return {
|
|
25502
|
+
schema_version: TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION,
|
|
25503
|
+
sourceRoots,
|
|
25504
|
+
sourceStores,
|
|
25505
|
+
include,
|
|
25506
|
+
exclude,
|
|
25507
|
+
limit,
|
|
25508
|
+
total_candidate_count: totalCandidateCount,
|
|
25509
|
+
returned_candidate_count: candidates.length,
|
|
25510
|
+
truncated: limit !== null && totalCandidateCount > candidates.length,
|
|
25511
|
+
stores,
|
|
25512
|
+
candidates,
|
|
25513
|
+
errors
|
|
25514
|
+
};
|
|
25515
|
+
}
|
|
24829
25516
|
|
|
24830
25517
|
// src/index.ts
|
|
24831
25518
|
init_database();
|
|
@@ -24963,8 +25650,8 @@ function listCyclesWithStats(options = {}, db) {
|
|
|
24963
25650
|
}
|
|
24964
25651
|
// src/lib/plan-artifacts.ts
|
|
24965
25652
|
init_database();
|
|
24966
|
-
import { existsSync as
|
|
24967
|
-
import { join as
|
|
25653
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
25654
|
+
import { join as join11, resolve as resolve11 } from "path";
|
|
24968
25655
|
var PLAN_MARKDOWN_SCHEMA = "hasna.todos.plan/v1";
|
|
24969
25656
|
function assertSafePathSegment(value, label) {
|
|
24970
25657
|
const trimmed = value.trim();
|
|
@@ -25000,12 +25687,15 @@ function projectSlugMatches(project, ref) {
|
|
|
25000
25687
|
const normalized = slugify(ref);
|
|
25001
25688
|
return Boolean(normalized) && (project.task_list_id === normalized || slugify(project.name) === normalized);
|
|
25002
25689
|
}
|
|
25690
|
+
function planArtifactSlug(plan) {
|
|
25691
|
+
return slugify(plan.slug || plan.name) || "plan";
|
|
25692
|
+
}
|
|
25003
25693
|
function resolvePlanArtifactProject(input) {
|
|
25004
25694
|
const db = input.db || getDatabase();
|
|
25005
25695
|
const ref = input.project_id || input.project_ref;
|
|
25006
25696
|
if (!ref)
|
|
25007
25697
|
throw new Error("Plan artifacts require a project id or project reference");
|
|
25008
|
-
const byPath = getProjectByPath(
|
|
25698
|
+
const byPath = getProjectByPath(resolve11(ref), db);
|
|
25009
25699
|
if (byPath)
|
|
25010
25700
|
return byPath;
|
|
25011
25701
|
const resolvedId = resolvePartialId(db, "projects", ref);
|
|
@@ -25022,14 +25712,27 @@ function resolvePlanArtifactProject(input) {
|
|
|
25022
25712
|
function resolvePlanArtifactPaths(input) {
|
|
25023
25713
|
const project = resolvePlanArtifactProject(input);
|
|
25024
25714
|
const projectId = assertSafePathSegment(project.id, "project id");
|
|
25025
|
-
const projectRoot =
|
|
25026
|
-
const directory =
|
|
25715
|
+
const projectRoot = resolve11(project.path);
|
|
25716
|
+
const directory = join11(projectRoot, ".hasna", "todos", "plans", projectId);
|
|
25027
25717
|
const planId = input.plan_id ? assertSafePathSegment(input.plan_id, "plan id") : null;
|
|
25718
|
+
const planSlug = input.plan_slug ? assertSafePathSegment(slugify(input.plan_slug), "plan slug") : null;
|
|
25719
|
+
const fileName = planId ? planSlug ? `${planSlug}--${planId.slice(0, 8)}.md` : `${planId}.md` : null;
|
|
25028
25720
|
return {
|
|
25029
25721
|
project_id: project.id,
|
|
25030
25722
|
project_root: projectRoot,
|
|
25031
25723
|
directory,
|
|
25032
|
-
file_path:
|
|
25724
|
+
file_path: fileName ? join11(directory, fileName) : directory
|
|
25725
|
+
};
|
|
25726
|
+
}
|
|
25727
|
+
function resolvePlanArtifactCandidatePaths(plan, db) {
|
|
25728
|
+
return {
|
|
25729
|
+
primary: resolvePlanArtifactPaths({
|
|
25730
|
+
project_id: plan.project_id,
|
|
25731
|
+
plan_id: plan.id,
|
|
25732
|
+
plan_slug: planArtifactSlug(plan),
|
|
25733
|
+
db
|
|
25734
|
+
}),
|
|
25735
|
+
legacy: resolvePlanArtifactPaths({ project_id: plan.project_id, plan_id: plan.id, db })
|
|
25033
25736
|
};
|
|
25034
25737
|
}
|
|
25035
25738
|
function buildPlanArtifactSnapshot(plan, tasks = [], artifactUpdatedAt = new Date().toISOString()) {
|
|
@@ -25046,6 +25749,7 @@ function buildPlanArtifactSnapshot(plan, tasks = [], artifactUpdatedAt = new Dat
|
|
|
25046
25749
|
metadata: {
|
|
25047
25750
|
schema: PLAN_MARKDOWN_SCHEMA,
|
|
25048
25751
|
plan_id: plan.id,
|
|
25752
|
+
plan_slug: plan.slug ?? null,
|
|
25049
25753
|
project_id: plan.project_id,
|
|
25050
25754
|
task_list_id: plan.task_list_id ?? null,
|
|
25051
25755
|
agent_id: plan.agent_id ?? null,
|
|
@@ -25085,6 +25789,7 @@ function renderPlanArtifactMarkdown(snapshot) {
|
|
|
25085
25789
|
"---",
|
|
25086
25790
|
`schema: ${frontmatterScalar(metadata.schema)}`,
|
|
25087
25791
|
`plan_id: ${frontmatterScalar(metadata.plan_id)}`,
|
|
25792
|
+
`plan_slug: ${frontmatterScalar(metadata.plan_slug)}`,
|
|
25088
25793
|
`project_id: ${frontmatterScalar(metadata.project_id)}`,
|
|
25089
25794
|
`task_list_id: ${frontmatterScalar(metadata.task_list_id)}`,
|
|
25090
25795
|
`agent_id: ${frontmatterScalar(metadata.agent_id)}`,
|
|
@@ -25130,6 +25835,7 @@ function parsePlanArtifactMarkdown(markdown) {
|
|
|
25130
25835
|
metadata: {
|
|
25131
25836
|
schema: PLAN_MARKDOWN_SCHEMA,
|
|
25132
25837
|
plan_id: rawMetadata.plan_id,
|
|
25838
|
+
plan_slug: rawMetadata.plan_slug ?? null,
|
|
25133
25839
|
project_id: rawMetadata.project_id,
|
|
25134
25840
|
task_list_id: rawMetadata.task_list_id ?? null,
|
|
25135
25841
|
agent_id: rawMetadata.agent_id ?? null,
|
|
@@ -25170,7 +25876,7 @@ function writePlanArtifact(plan, db) {
|
|
|
25170
25876
|
return null;
|
|
25171
25877
|
const d = db || getDatabase();
|
|
25172
25878
|
const tasks = listTasks({ plan_id: plan.id, include_archived: true }, d);
|
|
25173
|
-
const paths =
|
|
25879
|
+
const paths = resolvePlanArtifactCandidatePaths(plan, d).primary;
|
|
25174
25880
|
const snapshot = buildPlanArtifactSnapshot(plan, tasks);
|
|
25175
25881
|
mkdirSync8(paths.directory, { recursive: true });
|
|
25176
25882
|
writeFileSync6(paths.file_path, renderPlanArtifactMarkdown(snapshot), "utf8");
|
|
@@ -25180,12 +25886,13 @@ function readPlanArtifact(plan, db) {
|
|
|
25180
25886
|
if (!plan.project_id)
|
|
25181
25887
|
return null;
|
|
25182
25888
|
const d = db || getDatabase();
|
|
25183
|
-
const paths =
|
|
25184
|
-
|
|
25889
|
+
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
25890
|
+
const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
25891
|
+
if (!path)
|
|
25185
25892
|
return null;
|
|
25186
|
-
const markdown = readFileSync7(
|
|
25893
|
+
const markdown = readFileSync7(path, "utf8");
|
|
25187
25894
|
return {
|
|
25188
|
-
path
|
|
25895
|
+
path,
|
|
25189
25896
|
markdown,
|
|
25190
25897
|
...parsePlanArtifactMarkdown(markdown)
|
|
25191
25898
|
};
|
|
@@ -25194,10 +25901,11 @@ function inspectPlanArtifact(plan, db) {
|
|
|
25194
25901
|
if (!plan.project_id)
|
|
25195
25902
|
return null;
|
|
25196
25903
|
const d = db || getDatabase();
|
|
25197
|
-
const paths =
|
|
25198
|
-
|
|
25904
|
+
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
25905
|
+
const path = existsSync10(paths.primary.file_path) ? paths.primary.file_path : existsSync10(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
25906
|
+
if (!path) {
|
|
25199
25907
|
return {
|
|
25200
|
-
path: paths.file_path,
|
|
25908
|
+
path: paths.primary.file_path,
|
|
25201
25909
|
exists: false,
|
|
25202
25910
|
parse_error: null,
|
|
25203
25911
|
metadata: null,
|
|
@@ -25206,9 +25914,9 @@ function inspectPlanArtifact(plan, db) {
|
|
|
25206
25914
|
};
|
|
25207
25915
|
}
|
|
25208
25916
|
try {
|
|
25209
|
-
const artifact = parsePlanArtifactMarkdown(readFileSync7(
|
|
25917
|
+
const artifact = parsePlanArtifactMarkdown(readFileSync7(path, "utf8"));
|
|
25210
25918
|
return {
|
|
25211
|
-
path
|
|
25919
|
+
path,
|
|
25212
25920
|
exists: true,
|
|
25213
25921
|
parse_error: null,
|
|
25214
25922
|
metadata: artifact.metadata,
|
|
@@ -25217,7 +25925,7 @@ function inspectPlanArtifact(plan, db) {
|
|
|
25217
25925
|
};
|
|
25218
25926
|
} catch (error) {
|
|
25219
25927
|
return {
|
|
25220
|
-
path
|
|
25928
|
+
path,
|
|
25221
25929
|
exists: true,
|
|
25222
25930
|
parse_error: error instanceof Error ? error.message : String(error),
|
|
25223
25931
|
metadata: null,
|
|
@@ -25229,6 +25937,9 @@ function inspectPlanArtifact(plan, db) {
|
|
|
25229
25937
|
function comparePlanArtifact(plan, artifact, tasks) {
|
|
25230
25938
|
const conflicts = [];
|
|
25231
25939
|
compare("plan_id", plan.id, artifact.metadata.plan_id, conflicts);
|
|
25940
|
+
if (artifact.metadata.plan_slug !== null) {
|
|
25941
|
+
compare("plan_slug", plan.slug ?? null, artifact.metadata.plan_slug, conflicts);
|
|
25942
|
+
}
|
|
25232
25943
|
compare("project_id", plan.project_id ?? null, artifact.metadata.project_id, conflicts);
|
|
25233
25944
|
compare("name", plan.name, artifact.metadata.name, conflicts);
|
|
25234
25945
|
compare("status", plan.status, artifact.metadata.status, conflicts);
|
|
@@ -26277,28 +26988,28 @@ function renderRetrospectiveMarkdown(record) {
|
|
|
26277
26988
|
}
|
|
26278
26989
|
// src/lib/project-bootstrap.ts
|
|
26279
26990
|
init_database();
|
|
26280
|
-
import { existsSync as
|
|
26281
|
-
import { basename as
|
|
26991
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
26992
|
+
import { basename as basename3, dirname as dirname8, resolve as resolve12 } from "path";
|
|
26282
26993
|
function safeStat(path) {
|
|
26283
26994
|
try {
|
|
26284
|
-
return
|
|
26995
|
+
return statSync4(path);
|
|
26285
26996
|
} catch {
|
|
26286
26997
|
return null;
|
|
26287
26998
|
}
|
|
26288
26999
|
}
|
|
26289
27000
|
function canonicalPath(input) {
|
|
26290
|
-
const resolved =
|
|
27001
|
+
const resolved = resolve12(input);
|
|
26291
27002
|
const stats2 = safeStat(resolved);
|
|
26292
27003
|
if (stats2?.isFile())
|
|
26293
|
-
return
|
|
27004
|
+
return dirname8(resolved);
|
|
26294
27005
|
return resolved;
|
|
26295
27006
|
}
|
|
26296
27007
|
function findUp(start, marker) {
|
|
26297
27008
|
let current = canonicalPath(start);
|
|
26298
27009
|
while (true) {
|
|
26299
|
-
if (
|
|
27010
|
+
if (existsSync11(resolve12(current, marker)))
|
|
26300
27011
|
return current;
|
|
26301
|
-
const parent =
|
|
27012
|
+
const parent = dirname8(current);
|
|
26302
27013
|
if (parent === current)
|
|
26303
27014
|
return null;
|
|
26304
27015
|
current = parent;
|
|
@@ -26307,8 +27018,8 @@ function findUp(start, marker) {
|
|
|
26307
27018
|
function readPackageJson2(path) {
|
|
26308
27019
|
if (!path)
|
|
26309
27020
|
return null;
|
|
26310
|
-
const file =
|
|
26311
|
-
if (!
|
|
27021
|
+
const file = resolve12(path, "package.json");
|
|
27022
|
+
if (!existsSync11(file))
|
|
26312
27023
|
return null;
|
|
26313
27024
|
try {
|
|
26314
27025
|
const parsed = JSON.parse(readFileSync8(file, "utf-8"));
|
|
@@ -26319,9 +27030,9 @@ function readPackageJson2(path) {
|
|
|
26319
27030
|
}
|
|
26320
27031
|
function packageDisplayName(name, fallbackPath) {
|
|
26321
27032
|
if (!name)
|
|
26322
|
-
return
|
|
27033
|
+
return basename3(fallbackPath);
|
|
26323
27034
|
const withoutScope = name.startsWith("@") ? name.split("/")[1] : name;
|
|
26324
|
-
return withoutScope ||
|
|
27035
|
+
return withoutScope || basename3(fallbackPath);
|
|
26325
27036
|
}
|
|
26326
27037
|
function workspaceMarker(root, rootPackage) {
|
|
26327
27038
|
if (!root)
|
|
@@ -26330,7 +27041,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
26330
27041
|
if (rootPackage?.workspaces)
|
|
26331
27042
|
markers.push("package.json#workspaces");
|
|
26332
27043
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
26333
|
-
if (
|
|
27044
|
+
if (existsSync11(resolve12(root, marker)))
|
|
26334
27045
|
markers.push(marker);
|
|
26335
27046
|
}
|
|
26336
27047
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -26448,177 +27159,9 @@ function getProjectByPathForBootstrap(path, db) {
|
|
|
26448
27159
|
WHERE pmp.path = ?`).get(path);
|
|
26449
27160
|
return machineRow ?? null;
|
|
26450
27161
|
}
|
|
26451
|
-
// src/lib/task-routing.ts
|
|
26452
|
-
init_database();
|
|
26453
|
-
function classifyProjectKind2(path) {
|
|
26454
|
-
if (!path)
|
|
26455
|
-
return null;
|
|
26456
|
-
return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
|
|
26457
|
-
}
|
|
26458
|
-
function machineLocalPath(project, db) {
|
|
26459
|
-
const machineId = process.env["TODOS_MACHINE_ID"];
|
|
26460
|
-
if (!machineId)
|
|
26461
|
-
return null;
|
|
26462
|
-
try {
|
|
26463
|
-
const row = db.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
|
|
26464
|
-
return row?.path ?? null;
|
|
26465
|
-
} catch {
|
|
26466
|
-
return null;
|
|
26467
|
-
}
|
|
26468
|
-
}
|
|
26469
|
-
function resolveProject(task2, db) {
|
|
26470
|
-
const project = task2.project_id ? getProject(task2.project_id, db) : null;
|
|
26471
|
-
const projectPath = project ? machineLocalPath(project, db) ?? project.path : task2.working_dir;
|
|
26472
|
-
return { project, projectPath: projectPath ?? null };
|
|
26473
|
-
}
|
|
26474
|
-
function resolveTaskList(task2, project, db) {
|
|
26475
|
-
if (task2.task_list_id) {
|
|
26476
|
-
return getTaskList(task2.task_list_id, db) ?? (project ? getTaskListBySlug(task2.task_list_id, project.id, db) : null);
|
|
26477
|
-
}
|
|
26478
|
-
if (project?.task_list_id) {
|
|
26479
|
-
return getTaskListBySlug(project.task_list_id, project.id, db);
|
|
26480
|
-
}
|
|
26481
|
-
return null;
|
|
26482
|
-
}
|
|
26483
|
-
function isTerminal2(status) {
|
|
26484
|
-
return status === "completed" || status === "cancelled" || status === "failed";
|
|
26485
|
-
}
|
|
26486
|
-
function routeConcurrencyKey(task2, project, taskList, projectPath) {
|
|
26487
|
-
if (project?.id)
|
|
26488
|
-
return `project:${project.id}`;
|
|
26489
|
-
if (taskList?.id)
|
|
26490
|
-
return `task-list:${taskList.id}`;
|
|
26491
|
-
if (projectPath)
|
|
26492
|
-
return `path:${projectPath}`;
|
|
26493
|
-
return `task:${task2.id}`;
|
|
26494
|
-
}
|
|
26495
|
-
function getTaskRouteState(taskOrId, db) {
|
|
26496
|
-
const d = db || getDatabase();
|
|
26497
|
-
const task2 = typeof taskOrId === "string" ? getTask(taskOrId, d) : taskOrId;
|
|
26498
|
-
if (!task2)
|
|
26499
|
-
throw new Error(`Task not found: ${taskOrId}`);
|
|
26500
|
-
const { project, projectPath } = resolveProject(task2, d);
|
|
26501
|
-
const taskList = resolveTaskList(task2, project, d);
|
|
26502
|
-
const automation = routingAutomationMetadata(task2, taskList) ?? {};
|
|
26503
|
-
const routeEnabled = routeEnabledForTask(task2, taskList) === true;
|
|
26504
|
-
const tagOptIn = task2.tags.includes("auto:route") || task2.tags.includes("route:enabled");
|
|
26505
|
-
const locked = Boolean(task2.locked_by && !isLockExpired(task2.locked_at));
|
|
26506
|
-
const blockers = getBlockingDeps(task2.id, d);
|
|
26507
|
-
const blocked = blockers.length > 0;
|
|
26508
|
-
const terminal = isTerminal2(task2.status);
|
|
26509
|
-
const requiresApproval = automation.requires_approval === true || task2.requires_approval === true;
|
|
26510
|
-
const approvalRequired = automation.approval_required === true;
|
|
26511
|
-
const approved = Boolean(task2.approved_by);
|
|
26512
|
-
const gates = {
|
|
26513
|
-
route_enabled: routeEnabled,
|
|
26514
|
-
tag_opt_in: tagOptIn,
|
|
26515
|
-
no_auto: automation.no_auto === true,
|
|
26516
|
-
manual: automation.manual === true,
|
|
26517
|
-
manual_required: automation.manual_required === true,
|
|
26518
|
-
requires_approval: requiresApproval,
|
|
26519
|
-
approval_required: approvalRequired,
|
|
26520
|
-
approved,
|
|
26521
|
-
locked,
|
|
26522
|
-
blocked,
|
|
26523
|
-
terminal
|
|
26524
|
-
};
|
|
26525
|
-
const reasons = [];
|
|
26526
|
-
if (task2.status !== "pending")
|
|
26527
|
-
reasons.push("task_not_pending");
|
|
26528
|
-
if (terminal)
|
|
26529
|
-
reasons.push("task_terminal");
|
|
26530
|
-
if (!routeEnabled)
|
|
26531
|
-
reasons.push("route_not_enabled");
|
|
26532
|
-
if (locked)
|
|
26533
|
-
reasons.push("task_locked");
|
|
26534
|
-
if (blocked)
|
|
26535
|
-
reasons.push("task_blocked");
|
|
26536
|
-
if (gates.no_auto)
|
|
26537
|
-
reasons.push("no_auto");
|
|
26538
|
-
if (gates.manual)
|
|
26539
|
-
reasons.push("manual");
|
|
26540
|
-
if (gates.manual_required)
|
|
26541
|
-
reasons.push("manual_required");
|
|
26542
|
-
if (requiresApproval && !approved)
|
|
26543
|
-
reasons.push("requires_approval");
|
|
26544
|
-
if (approvalRequired && !approved)
|
|
26545
|
-
reasons.push("approval_required");
|
|
26546
|
-
if (automation.allowed === false)
|
|
26547
|
-
reasons.push("automation_disallowed");
|
|
26548
|
-
return {
|
|
26549
|
-
schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
|
|
26550
|
-
task_id: task2.id,
|
|
26551
|
-
task_short_id: task2.short_id,
|
|
26552
|
-
status: task2.status,
|
|
26553
|
-
eligible: reasons.length === 0,
|
|
26554
|
-
reasons,
|
|
26555
|
-
blockers: blockers.map((blocker) => ({
|
|
26556
|
-
id: blocker.id,
|
|
26557
|
-
short_id: blocker.short_id,
|
|
26558
|
-
title: blocker.title,
|
|
26559
|
-
status: blocker.status
|
|
26560
|
-
})),
|
|
26561
|
-
gates,
|
|
26562
|
-
automation: Object.keys(automation).length > 0 ? automation : null,
|
|
26563
|
-
route: {
|
|
26564
|
-
project_id: project?.id ?? task2.project_id,
|
|
26565
|
-
project_path: projectPath,
|
|
26566
|
-
working_dir: task2.working_dir ?? projectPath,
|
|
26567
|
-
project_kind: classifyProjectKind2(projectPath),
|
|
26568
|
-
task_list_id: taskList?.id ?? task2.task_list_id,
|
|
26569
|
-
task_list_slug: taskList?.slug ?? null,
|
|
26570
|
-
task_list_name: taskList?.name ?? null,
|
|
26571
|
-
concurrency_key: routeConcurrencyKey(task2, project, taskList, projectPath)
|
|
26572
|
-
},
|
|
26573
|
-
pointers: workflowPointersFromMetadata(task2.metadata)
|
|
26574
|
-
};
|
|
26575
|
-
}
|
|
26576
|
-
function setTaskWorkflowPointers(taskId, input, db) {
|
|
26577
|
-
const d = db || getDatabase();
|
|
26578
|
-
const task2 = getTask(taskId, d);
|
|
26579
|
-
if (!task2)
|
|
26580
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
26581
|
-
const previous = workflowPointersFromMetadata(task2.metadata);
|
|
26582
|
-
const next = compactWorkflowPointers({
|
|
26583
|
-
current_workflow_invocation_id: pointerPatch(previous.current_workflow_invocation_id, input, "current_workflow_invocation_id"),
|
|
26584
|
-
current_run_id: pointerPatch(previous.current_run_id, input, "current_run_id"),
|
|
26585
|
-
latest_manifest_path: pointerPatch(previous.latest_manifest_path, input, "latest_manifest_path"),
|
|
26586
|
-
latest_evaluation_path: pointerPatch(previous.latest_evaluation_path, input, "latest_evaluation_path"),
|
|
26587
|
-
workflow_state: pointerPatch(previous.workflow_state, input, "workflow_state")
|
|
26588
|
-
});
|
|
26589
|
-
const timestamp2 = now();
|
|
26590
|
-
const {
|
|
26591
|
-
current_workflow_invocation_id,
|
|
26592
|
-
current_run_id,
|
|
26593
|
-
latest_manifest_path,
|
|
26594
|
-
latest_evaluation_path,
|
|
26595
|
-
workflow_state,
|
|
26596
|
-
workflow_invocation,
|
|
26597
|
-
...baseMetadata
|
|
26598
|
-
} = task2.metadata;
|
|
26599
|
-
const metadata = {
|
|
26600
|
-
...baseMetadata,
|
|
26601
|
-
...next,
|
|
26602
|
-
workflow_invocation: {
|
|
26603
|
-
schema_version: TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
|
|
26604
|
-
...next,
|
|
26605
|
-
updated_at: timestamp2,
|
|
26606
|
-
updated_by: input.actor ?? null
|
|
26607
|
-
}
|
|
26608
|
-
};
|
|
26609
|
-
return updateTask(task2.id, { version: task2.version, metadata }, d);
|
|
26610
|
-
}
|
|
26611
|
-
function pointerPatch(previous, input, key) {
|
|
26612
|
-
if (!Object.prototype.hasOwnProperty.call(input, key))
|
|
26613
|
-
return previous;
|
|
26614
|
-
const value = input[key];
|
|
26615
|
-
if (value === undefined)
|
|
26616
|
-
return previous;
|
|
26617
|
-
return typeof value === "string" && value.trim() ? value : undefined;
|
|
26618
|
-
}
|
|
26619
27162
|
// src/db/api-keys.ts
|
|
26620
27163
|
init_database();
|
|
26621
|
-
import { createHash as
|
|
27164
|
+
import { createHash as createHash11, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
26622
27165
|
function rowToRecord(row) {
|
|
26623
27166
|
return {
|
|
26624
27167
|
id: row.id,
|
|
@@ -26632,7 +27175,7 @@ function rowToRecord(row) {
|
|
|
26632
27175
|
};
|
|
26633
27176
|
}
|
|
26634
27177
|
function hashApiKey(key) {
|
|
26635
|
-
return
|
|
27178
|
+
return createHash11("sha256").update(key).digest("hex");
|
|
26636
27179
|
}
|
|
26637
27180
|
function safeEqualHex(a, b) {
|
|
26638
27181
|
if (a.length !== b.length)
|
|
@@ -26823,18 +27366,18 @@ var gatherTrainingData = async (options = {}) => {
|
|
|
26823
27366
|
};
|
|
26824
27367
|
// src/lib/model-config.ts
|
|
26825
27368
|
init_sync_utils();
|
|
26826
|
-
import { existsSync as
|
|
26827
|
-
import { join as
|
|
27369
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
27370
|
+
import { join as join12 } from "path";
|
|
26828
27371
|
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
26829
27372
|
function getConfigDir() {
|
|
26830
27373
|
return getTodosGlobalDir();
|
|
26831
27374
|
}
|
|
26832
27375
|
function getConfigPath2() {
|
|
26833
|
-
return
|
|
27376
|
+
return join12(getConfigDir(), "config.json");
|
|
26834
27377
|
}
|
|
26835
27378
|
function readConfig() {
|
|
26836
27379
|
const configPath = getConfigPath2();
|
|
26837
|
-
if (!
|
|
27380
|
+
if (!existsSync12(configPath))
|
|
26838
27381
|
return {};
|
|
26839
27382
|
try {
|
|
26840
27383
|
const raw = readFileSync9(configPath, "utf-8");
|
|
@@ -26845,7 +27388,7 @@ function readConfig() {
|
|
|
26845
27388
|
}
|
|
26846
27389
|
function writeConfig(config) {
|
|
26847
27390
|
const configDir = getConfigDir();
|
|
26848
|
-
if (!
|
|
27391
|
+
if (!existsSync12(configDir)) {
|
|
26849
27392
|
mkdirSync9(configDir, { recursive: true });
|
|
26850
27393
|
}
|
|
26851
27394
|
writeFileSync7(getConfigPath2(), JSON.stringify(config, null, 2) + `
|
|
@@ -27570,7 +28113,7 @@ CLI equivalent: \`${r.equivalent_cli}\`
|
|
|
27570
28113
|
`);
|
|
27571
28114
|
}
|
|
27572
28115
|
// src/lib/verification-providers.ts
|
|
27573
|
-
import { existsSync as
|
|
28116
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
27574
28117
|
init_database();
|
|
27575
28118
|
init_config();
|
|
27576
28119
|
init_redaction();
|
|
@@ -27681,7 +28224,7 @@ function classifyLog(text) {
|
|
|
27681
28224
|
async function sleep2(ms) {
|
|
27682
28225
|
if (ms <= 0)
|
|
27683
28226
|
return;
|
|
27684
|
-
await new Promise((
|
|
28227
|
+
await new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
27685
28228
|
}
|
|
27686
28229
|
async function runCommandProvider(provider, input) {
|
|
27687
28230
|
const commandTemplate = input.command || provider.command;
|
|
@@ -27736,7 +28279,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
27736
28279
|
};
|
|
27737
28280
|
}
|
|
27738
28281
|
function runCiLogProvider(input) {
|
|
27739
|
-
const text = input.log_text ?? (input.log_path &&
|
|
28282
|
+
const text = input.log_text ?? (input.log_path && existsSync13(input.log_path) ? readFileSync10(input.log_path, "utf-8") : "");
|
|
27740
28283
|
return {
|
|
27741
28284
|
status: classifyLog(text),
|
|
27742
28285
|
attempts: 1,
|
|
@@ -27748,7 +28291,7 @@ function runBrowserProvider(input) {
|
|
|
27748
28291
|
if (!input.artifact_path) {
|
|
27749
28292
|
return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
|
|
27750
28293
|
}
|
|
27751
|
-
if (!
|
|
28294
|
+
if (!existsSync13(input.artifact_path)) {
|
|
27752
28295
|
return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
|
|
27753
28296
|
}
|
|
27754
28297
|
return {
|
|
@@ -27913,7 +28456,7 @@ function listVerificationRecords(filter = {}, db) {
|
|
|
27913
28456
|
// src/lib/verification-evidence.ts
|
|
27914
28457
|
init_database();
|
|
27915
28458
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10 } from "fs";
|
|
27916
|
-
import { dirname as
|
|
28459
|
+
import { dirname as dirname9 } from "path";
|
|
27917
28460
|
var VERIFICATION_EVIDENCE_SCHEMA = "todos.verification_evidence.v1";
|
|
27918
28461
|
function getMachineId3() {
|
|
27919
28462
|
return process.env["TODOS_MACHINE_ID"] || __require("os").hostname();
|
|
@@ -28033,15 +28576,15 @@ function exportVerificationEvidence(filter = {}, db) {
|
|
|
28033
28576
|
};
|
|
28034
28577
|
}
|
|
28035
28578
|
function writeVerificationExport(bundle, path) {
|
|
28036
|
-
mkdirSync10(
|
|
28579
|
+
mkdirSync10(dirname9(path), { recursive: true });
|
|
28037
28580
|
writeFileSync8(path, JSON.stringify(bundle, null, 2), "utf8");
|
|
28038
28581
|
}
|
|
28039
28582
|
// src/lib/policy-packs.ts
|
|
28040
|
-
import { relative as relative3, resolve as
|
|
28583
|
+
import { relative as relative3, resolve as resolve13 } from "path";
|
|
28041
28584
|
init_database();
|
|
28042
28585
|
init_config();
|
|
28043
|
-
function
|
|
28044
|
-
return
|
|
28586
|
+
function normalizePath4(path) {
|
|
28587
|
+
return resolve13(path);
|
|
28045
28588
|
}
|
|
28046
28589
|
function unique4(values) {
|
|
28047
28590
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -28051,13 +28594,13 @@ function parseStatuses(values) {
|
|
|
28051
28594
|
return unique4(values).filter((value) => allowed.has(value));
|
|
28052
28595
|
}
|
|
28053
28596
|
function configuredPacks(config = loadConfig()) {
|
|
28054
|
-
return Object.values(config.policy_packs || {}).map((pack) => ({ ...pack, root:
|
|
28597
|
+
return Object.values(config.policy_packs || {}).map((pack) => ({ ...pack, root: normalizePath4(pack.root) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
28055
28598
|
}
|
|
28056
28599
|
function defaultPolicyPack(name, root) {
|
|
28057
28600
|
return {
|
|
28058
28601
|
name,
|
|
28059
28602
|
version: 1,
|
|
28060
|
-
root:
|
|
28603
|
+
root: normalizePath4(root),
|
|
28061
28604
|
required_commands: [],
|
|
28062
28605
|
prohibited_commands: ["npm install -g", "git reset --hard", "git checkout --", "rm -rf"],
|
|
28063
28606
|
prohibited_paths: [],
|
|
@@ -28075,7 +28618,7 @@ function isPathInside3(root, path) {
|
|
|
28075
28618
|
const rel = relative3(root, path);
|
|
28076
28619
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/") && !/^[A-Za-z]:/.test(rel);
|
|
28077
28620
|
}
|
|
28078
|
-
function
|
|
28621
|
+
function matchesPattern4(value, pattern) {
|
|
28079
28622
|
const normalizedValue = value.toLowerCase();
|
|
28080
28623
|
const normalizedPattern = pattern.toLowerCase();
|
|
28081
28624
|
if (pattern.startsWith("/") && pattern.endsWith("/") && pattern.length > 2) {
|
|
@@ -28092,14 +28635,14 @@ function matchesPattern3(value, pattern) {
|
|
|
28092
28635
|
return normalizedValue === normalizedPattern || normalizedValue.includes(normalizedPattern);
|
|
28093
28636
|
}
|
|
28094
28637
|
function commandMatches(commands, pattern) {
|
|
28095
|
-
return commands.filter((command) =>
|
|
28638
|
+
return commands.filter((command) => matchesPattern4(command, pattern));
|
|
28096
28639
|
}
|
|
28097
28640
|
function pathMatches(paths, pattern, root) {
|
|
28098
28641
|
return paths.filter((path) => {
|
|
28099
|
-
const candidate = path.startsWith("/") ? path :
|
|
28642
|
+
const candidate = path.startsWith("/") ? path : resolve13(root, path);
|
|
28100
28643
|
if (!isPathInside3(root, candidate))
|
|
28101
|
-
return
|
|
28102
|
-
return
|
|
28644
|
+
return matchesPattern4(path, pattern);
|
|
28645
|
+
return matchesPattern4(path, pattern) || matchesPattern4(relative3(root, candidate), pattern);
|
|
28103
28646
|
});
|
|
28104
28647
|
}
|
|
28105
28648
|
function finding(id, passed, message, evidence = []) {
|
|
@@ -28178,7 +28721,7 @@ function getPolicyPack(name) {
|
|
|
28178
28721
|
function upsertPolicyPack(input) {
|
|
28179
28722
|
const config = loadConfig();
|
|
28180
28723
|
const existing = config.policy_packs?.[input.name];
|
|
28181
|
-
const root =
|
|
28724
|
+
const root = normalizePath4(input.root || existing?.root || process.cwd());
|
|
28182
28725
|
const base = existing || defaultPolicyPack(input.name, root);
|
|
28183
28726
|
const timestamp2 = new Date().toISOString();
|
|
28184
28727
|
const pack = {
|
|
@@ -28258,7 +28801,7 @@ function validatePolicyPack(input, db) {
|
|
|
28258
28801
|
findings.push(finding("linked-pull-request", refs.length > 0, "at least one linked pull request is required", refs.map((ref) => ref.name)));
|
|
28259
28802
|
}
|
|
28260
28803
|
if (pack.branch_pattern) {
|
|
28261
|
-
const branches = evidence.gitRefs.filter((ref) => ref.ref_type === "branch" &&
|
|
28804
|
+
const branches = evidence.gitRefs.filter((ref) => ref.ref_type === "branch" && matchesPattern4(ref.name, pack.branch_pattern));
|
|
28262
28805
|
findings.push(finding("branch-pattern", branches.length > 0, `at least one linked branch must match: ${pack.branch_pattern}`, branches.map((ref) => ref.name)));
|
|
28263
28806
|
}
|
|
28264
28807
|
if (pack.require_approval) {
|
|
@@ -28387,21 +28930,21 @@ function resourceDiagnostics() {
|
|
|
28387
28930
|
};
|
|
28388
28931
|
}
|
|
28389
28932
|
// src/lib/sandbox-profiles.ts
|
|
28390
|
-
import { existsSync as
|
|
28391
|
-
import { join as
|
|
28933
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11 } from "fs";
|
|
28934
|
+
import { join as join13, dirname as dirname10 } from "path";
|
|
28392
28935
|
var SANDBOX_PROFILE_VERSION = "todos.sandbox-profile.v1";
|
|
28393
28936
|
function getProfilesPath() {
|
|
28394
28937
|
if (process.env["TODOS_SANDBOX_PROFILES_PATH"]) {
|
|
28395
28938
|
return process.env["TODOS_SANDBOX_PROFILES_PATH"];
|
|
28396
28939
|
}
|
|
28397
|
-
const localDir =
|
|
28398
|
-
const local =
|
|
28399
|
-
if (
|
|
28940
|
+
const localDir = join13(process.cwd(), ".todos");
|
|
28941
|
+
const local = join13(localDir, "sandbox-profiles.json");
|
|
28942
|
+
if (existsSync14(localDir))
|
|
28400
28943
|
return local;
|
|
28401
|
-
if (
|
|
28944
|
+
if (existsSync14(local))
|
|
28402
28945
|
return local;
|
|
28403
28946
|
const home = process.env["HOME"] || "~";
|
|
28404
|
-
return
|
|
28947
|
+
return join13(home, ".hasna", "todos", "sandbox-profiles.json");
|
|
28405
28948
|
}
|
|
28406
28949
|
var cached2 = null;
|
|
28407
28950
|
function resetSandboxProfileCache() {
|
|
@@ -28433,7 +28976,7 @@ function loadSandboxProfiles() {
|
|
|
28433
28976
|
if (cached2)
|
|
28434
28977
|
return cached2;
|
|
28435
28978
|
const path = getProfilesPath();
|
|
28436
|
-
if (!
|
|
28979
|
+
if (!existsSync14(path)) {
|
|
28437
28980
|
cached2 = getDefaultSandboxProfiles();
|
|
28438
28981
|
return cached2;
|
|
28439
28982
|
}
|
|
@@ -28446,7 +28989,7 @@ function getSandboxProfile(name) {
|
|
|
28446
28989
|
}
|
|
28447
28990
|
function saveSandboxProfiles(profiles) {
|
|
28448
28991
|
const path = getProfilesPath();
|
|
28449
|
-
mkdirSync11(
|
|
28992
|
+
mkdirSync11(dirname10(path), { recursive: true });
|
|
28450
28993
|
writeFileSync9(path, JSON.stringify({ schema_version: SANDBOX_PROFILE_VERSION, profiles }, null, 2));
|
|
28451
28994
|
cached2 = profiles;
|
|
28452
28995
|
}
|
|
@@ -28806,9 +29349,9 @@ function getDefaultAgentAdapters() {
|
|
|
28806
29349
|
}
|
|
28807
29350
|
function resetAgentAdapterCache() {}
|
|
28808
29351
|
// src/lib/git-traceability.ts
|
|
28809
|
-
import { existsSync as
|
|
29352
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
|
|
28810
29353
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
28811
|
-
import { resolve as
|
|
29354
|
+
import { resolve as resolve14 } from "path";
|
|
28812
29355
|
var GIT_TRACEABILITY_SCHEMA_VERSION = "todos.git_traceability.v1";
|
|
28813
29356
|
function runGit(args, cwd) {
|
|
28814
29357
|
const result = spawnSync2("git", args, { cwd, encoding: "utf8" });
|
|
@@ -28851,8 +29394,8 @@ function inspectGitCommit(sha, cwd) {
|
|
|
28851
29394
|
};
|
|
28852
29395
|
}
|
|
28853
29396
|
function loadCiSnapshot(path) {
|
|
28854
|
-
const target = path ?
|
|
28855
|
-
if (!
|
|
29397
|
+
const target = path ? resolve14(path) : resolve14(process.cwd(), ".todos", "ci-snapshot.json");
|
|
29398
|
+
if (!existsSync15(target))
|
|
28856
29399
|
return null;
|
|
28857
29400
|
try {
|
|
28858
29401
|
const parsed = JSON.parse(readFileSync12(target, "utf8"));
|
|
@@ -28948,8 +29491,8 @@ function formatTraceabilityReport(report) {
|
|
|
28948
29491
|
`);
|
|
28949
29492
|
}
|
|
28950
29493
|
// src/lib/mention-resolver.ts
|
|
28951
|
-
import { existsSync as
|
|
28952
|
-
import { basename as
|
|
29494
|
+
import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync13, statSync as statSync5 } from "fs";
|
|
29495
|
+
import { basename as basename4, isAbsolute, join as join14, relative as relative4, resolve as resolve15, sep as sep2 } from "path";
|
|
28953
29496
|
init_database();
|
|
28954
29497
|
var PREFIXES = {
|
|
28955
29498
|
file: "file",
|
|
@@ -29025,7 +29568,7 @@ function backlink(kind, key, label, target = key) {
|
|
|
29025
29568
|
return { kind, key, label, target };
|
|
29026
29569
|
}
|
|
29027
29570
|
function normalizeWorkspace(workspace) {
|
|
29028
|
-
return
|
|
29571
|
+
return resolve15(workspace || process.cwd());
|
|
29029
29572
|
}
|
|
29030
29573
|
function isInside(root, absolutePath) {
|
|
29031
29574
|
const rel = relative4(root, absolutePath);
|
|
@@ -29093,18 +29636,18 @@ function resolveFile(parsed, workspace) {
|
|
|
29093
29636
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
29094
29637
|
return resolution;
|
|
29095
29638
|
}
|
|
29096
|
-
const absolutePath =
|
|
29639
|
+
const absolutePath = resolve15(workspace, relPath);
|
|
29097
29640
|
if (!isInside(workspace, absolutePath)) {
|
|
29098
29641
|
resolution.path = relPath;
|
|
29099
29642
|
resolution.warnings.push("path escapes the workspace");
|
|
29100
29643
|
return resolution;
|
|
29101
29644
|
}
|
|
29102
29645
|
resolution.path = relPath;
|
|
29103
|
-
if (!
|
|
29646
|
+
if (!existsSync16(absolutePath)) {
|
|
29104
29647
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
29105
29648
|
return resolution;
|
|
29106
29649
|
}
|
|
29107
|
-
const stats2 =
|
|
29650
|
+
const stats2 = statSync5(absolutePath);
|
|
29108
29651
|
if (!stats2.isFile()) {
|
|
29109
29652
|
resolution.warnings.push("path exists but is not a file");
|
|
29110
29653
|
return resolution;
|
|
@@ -29128,12 +29671,12 @@ function resolveFile(parsed, workspace) {
|
|
|
29128
29671
|
function walkSourceFiles(root, current = root, files = []) {
|
|
29129
29672
|
if (files.length >= 5000)
|
|
29130
29673
|
return files;
|
|
29131
|
-
for (const entry2 of
|
|
29674
|
+
for (const entry2 of readdirSync3(current, { withFileTypes: true })) {
|
|
29132
29675
|
if (entry2.name.startsWith(".") && ![".github"].includes(entry2.name)) {
|
|
29133
29676
|
if (SKIP_DIRS.has(entry2.name))
|
|
29134
29677
|
continue;
|
|
29135
29678
|
}
|
|
29136
|
-
const absolutePath =
|
|
29679
|
+
const absolutePath = join14(current, entry2.name);
|
|
29137
29680
|
if (entry2.isDirectory()) {
|
|
29138
29681
|
if (!SKIP_DIRS.has(entry2.name))
|
|
29139
29682
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -29141,8 +29684,8 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
29141
29684
|
}
|
|
29142
29685
|
if (!entry2.isFile())
|
|
29143
29686
|
continue;
|
|
29144
|
-
const extension = `.${
|
|
29145
|
-
if (SOURCE_EXTENSIONS.has(extension) &&
|
|
29687
|
+
const extension = `.${basename4(entry2.name).split(".").pop() || ""}`;
|
|
29688
|
+
if (SOURCE_EXTENSIONS.has(extension) && statSync5(absolutePath).size <= 512 * 1024) {
|
|
29146
29689
|
files.push(absolutePath);
|
|
29147
29690
|
}
|
|
29148
29691
|
}
|
|
@@ -31013,9 +31556,9 @@ function getAdapterDocsFingerprint() {
|
|
|
31013
31556
|
}
|
|
31014
31557
|
// src/lib/inbox-intake.ts
|
|
31015
31558
|
init_database();
|
|
31016
|
-
import { existsSync as
|
|
31017
|
-
import { basename as
|
|
31018
|
-
import { createHash as
|
|
31559
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
31560
|
+
import { basename as basename5 } from "path";
|
|
31561
|
+
import { createHash as createHash12 } from "crypto";
|
|
31019
31562
|
init_secret_redaction();
|
|
31020
31563
|
var INBOX_INTAKE_SCHEMA = "todos.inbox_intake.v1";
|
|
31021
31564
|
var INTAKE_SOURCE_TYPES = [
|
|
@@ -31028,7 +31571,7 @@ var INTAKE_SOURCE_TYPES = [
|
|
|
31028
31571
|
];
|
|
31029
31572
|
var INTAKE_TRIAGE_STATUSES = ["preview", "triaged", "duplicate", "created"];
|
|
31030
31573
|
function fingerprint2(text) {
|
|
31031
|
-
return
|
|
31574
|
+
return createHash12("sha256").update(text).digest("hex").slice(0, 16);
|
|
31032
31575
|
}
|
|
31033
31576
|
function loadRawContent(input) {
|
|
31034
31577
|
if (input.github_url) {
|
|
@@ -31059,15 +31602,15 @@ function loadRawContent(input) {
|
|
|
31059
31602
|
}
|
|
31060
31603
|
}
|
|
31061
31604
|
if (input.file_path) {
|
|
31062
|
-
if (!
|
|
31605
|
+
if (!existsSync17(input.file_path))
|
|
31063
31606
|
throw new Error(`File not found: ${input.file_path}`);
|
|
31064
31607
|
const raw = readFileSync14(input.file_path, "utf8");
|
|
31065
|
-
const name =
|
|
31608
|
+
const name = basename5(input.file_path).toLowerCase();
|
|
31066
31609
|
const source_type2 = input.source_type ?? (name.includes("ci") || name.endsWith(".log") ? "ci_log" : "file");
|
|
31067
31610
|
return {
|
|
31068
31611
|
raw,
|
|
31069
31612
|
source_type: source_type2,
|
|
31070
|
-
metadata: { file_path: input.file_path, file_name:
|
|
31613
|
+
metadata: { file_path: input.file_path, file_name: basename5(input.file_path) }
|
|
31071
31614
|
};
|
|
31072
31615
|
}
|
|
31073
31616
|
const text = input.text?.trim();
|
|
@@ -31677,7 +32220,7 @@ function formatNlIntakePreviewText(preview) {
|
|
|
31677
32220
|
}
|
|
31678
32221
|
// src/lib/issue-importers.ts
|
|
31679
32222
|
init_database();
|
|
31680
|
-
import { existsSync as
|
|
32223
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
31681
32224
|
var ISSUE_IMPORT_SCHEMA = "todos.issue_import.v1";
|
|
31682
32225
|
var ISSUE_SOURCES = ["github", "linear", "jira", "auto"];
|
|
31683
32226
|
var GITHUB_LABEL_PRIORITY = {
|
|
@@ -31896,7 +32439,7 @@ function parseIssueExport(data, source9 = "auto") {
|
|
|
31896
32439
|
return normalized;
|
|
31897
32440
|
}
|
|
31898
32441
|
function loadIssueExportFromFile(path) {
|
|
31899
|
-
if (!
|
|
32442
|
+
if (!existsSync18(path))
|
|
31900
32443
|
throw new Error(`File not found: ${path}`);
|
|
31901
32444
|
return JSON.parse(readFileSync15(path, "utf8"));
|
|
31902
32445
|
}
|
|
@@ -32056,8 +32599,8 @@ todos import issues ./linear.json --source linear --dry-run
|
|
|
32056
32599
|
// src/lib/run-records.ts
|
|
32057
32600
|
init_database();
|
|
32058
32601
|
init_secret_redaction();
|
|
32059
|
-
import { existsSync as
|
|
32060
|
-
import { join as
|
|
32602
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
32603
|
+
import { join as join15, dirname as dirname11 } from "path";
|
|
32061
32604
|
var RUN_RECORD_SCHEMA = "todos.run_record.v1";
|
|
32062
32605
|
var RUN_RECORD_STATUSES = ["active", "completed", "failed", "archived"];
|
|
32063
32606
|
function parseJsonArray3(raw, fallback = []) {
|
|
@@ -32250,8 +32793,8 @@ function buildRunReplayBundle(id, db) {
|
|
|
32250
32793
|
}
|
|
32251
32794
|
function exportRunReplay(id, outputPath, db) {
|
|
32252
32795
|
const bundle = buildRunReplayBundle(id, db);
|
|
32253
|
-
const path = outputPath ??
|
|
32254
|
-
mkdirSync12(
|
|
32796
|
+
const path = outputPath ?? join15(process.cwd(), ".todos", "replays", `${id.slice(0, 8)}.json`);
|
|
32797
|
+
mkdirSync12(dirname11(path), { recursive: true });
|
|
32255
32798
|
writeFileSync10(path, JSON.stringify(bundle, null, 2));
|
|
32256
32799
|
const d = db || getDatabase();
|
|
32257
32800
|
d.run(`UPDATE run_records SET replay_bundle = ?, updated_at = ? WHERE id = ?`, [path, now(), id]);
|
|
@@ -32290,16 +32833,16 @@ function formatRunRecordMarkdown(record) {
|
|
|
32290
32833
|
`;
|
|
32291
32834
|
}
|
|
32292
32835
|
function getDefaultReplayDir() {
|
|
32293
|
-
const local =
|
|
32294
|
-
if (
|
|
32836
|
+
const local = join15(process.cwd(), ".todos", "replays");
|
|
32837
|
+
if (existsSync19(join15(process.cwd(), ".todos")))
|
|
32295
32838
|
return local;
|
|
32296
32839
|
const home = process.env["HOME"] || "~";
|
|
32297
|
-
return
|
|
32840
|
+
return join15(home, ".hasna", "todos", "replays");
|
|
32298
32841
|
}
|
|
32299
32842
|
// src/lib/release-checks.ts
|
|
32300
32843
|
init_secret_redaction();
|
|
32301
|
-
import { existsSync as
|
|
32302
|
-
import { join as
|
|
32844
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
|
|
32845
|
+
import { join as join16, relative as relative5 } from "path";
|
|
32303
32846
|
var RELEASE_CHECK_SCHEMA = "todos.release_check.v1";
|
|
32304
32847
|
var FORBIDDEN_DIST_PATTERNS = [
|
|
32305
32848
|
{
|
|
@@ -32312,17 +32855,17 @@ var FORBIDDEN_DIST_PATTERNS = [
|
|
|
32312
32855
|
];
|
|
32313
32856
|
var REQUIRED_BINS = ["todos", "todos-mcp", "todos-serve"];
|
|
32314
32857
|
function readPackageJson3(root) {
|
|
32315
|
-
const path =
|
|
32316
|
-
if (!
|
|
32858
|
+
const path = join16(root, "package.json");
|
|
32859
|
+
if (!existsSync20(path))
|
|
32317
32860
|
throw new Error(`package.json not found in ${root}`);
|
|
32318
32861
|
return JSON.parse(readFileSync16(path, "utf8"));
|
|
32319
32862
|
}
|
|
32320
32863
|
function walkFiles(dir, acc = []) {
|
|
32321
|
-
if (!
|
|
32864
|
+
if (!existsSync20(dir))
|
|
32322
32865
|
return acc;
|
|
32323
|
-
for (const entry2 of
|
|
32324
|
-
const full =
|
|
32325
|
-
const st =
|
|
32866
|
+
for (const entry2 of readdirSync4(dir)) {
|
|
32867
|
+
const full = join16(dir, entry2);
|
|
32868
|
+
const st = statSync6(full);
|
|
32326
32869
|
if (st.isDirectory())
|
|
32327
32870
|
walkFiles(full, acc);
|
|
32328
32871
|
else if (/\.(js|mjs|cjs|json|d\.ts)$/.test(entry2))
|
|
@@ -32339,8 +32882,8 @@ function auditPackageContents(root) {
|
|
|
32339
32882
|
checks.push({ id: "files_dist", severity: "error", message: "package.json files must include dist" });
|
|
32340
32883
|
}
|
|
32341
32884
|
for (const pattern of files) {
|
|
32342
|
-
const target =
|
|
32343
|
-
if (!
|
|
32885
|
+
const target = join16(root, pattern);
|
|
32886
|
+
if (!existsSync20(target)) {
|
|
32344
32887
|
checks.push({ id: `files_missing_${pattern}`, severity: "error", message: `Published file path missing: ${pattern}` });
|
|
32345
32888
|
}
|
|
32346
32889
|
}
|
|
@@ -32354,8 +32897,8 @@ function auditPackageContents(root) {
|
|
|
32354
32897
|
checks.push({ id: `bin_${name}`, severity: "error", message: `Missing bin entry: ${name}` });
|
|
32355
32898
|
continue;
|
|
32356
32899
|
}
|
|
32357
|
-
const binPath =
|
|
32358
|
-
if (!
|
|
32900
|
+
const binPath = join16(root, rel);
|
|
32901
|
+
if (!existsSync20(binPath)) {
|
|
32359
32902
|
checks.push({ id: `bin_path_${name}`, severity: "error", message: `Bin file missing: ${rel}` });
|
|
32360
32903
|
} else {
|
|
32361
32904
|
checks.push({ id: `bin_ok_${name}`, severity: "info", message: `Bin present: ${name} \u2192 ${rel}` });
|
|
@@ -32372,8 +32915,8 @@ function auditPackageContents(root) {
|
|
|
32372
32915
|
}
|
|
32373
32916
|
function scanDistArtifacts(root) {
|
|
32374
32917
|
const checks = [];
|
|
32375
|
-
const distDir =
|
|
32376
|
-
if (!
|
|
32918
|
+
const distDir = join16(root, "dist");
|
|
32919
|
+
if (!existsSync20(distDir)) {
|
|
32377
32920
|
checks.push({ id: "dist_missing", severity: "error", message: "dist/ directory not found \u2014 run bun run build" });
|
|
32378
32921
|
return checks;
|
|
32379
32922
|
}
|
|
@@ -32679,15 +33222,15 @@ function renderReleaseNotesMarkdown(document) {
|
|
|
32679
33222
|
// src/lib/db-backup.ts
|
|
32680
33223
|
init_database();
|
|
32681
33224
|
init_migrations();
|
|
32682
|
-
import { existsSync as
|
|
32683
|
-
import { dirname as
|
|
32684
|
-
import { Database as
|
|
33225
|
+
import { existsSync as existsSync21, copyFileSync, mkdirSync as mkdirSync13, readFileSync as readFileSync17, statSync as statSync7, writeFileSync as writeFileSync11, unlinkSync } from "fs";
|
|
33226
|
+
import { dirname as dirname12, join as join17, resolve as resolve16 } from "path";
|
|
33227
|
+
import { Database as Database4 } from "bun:sqlite";
|
|
32685
33228
|
var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
|
|
32686
33229
|
function resolveDbPath(dbPath) {
|
|
32687
33230
|
if (dbPath)
|
|
32688
|
-
return
|
|
33231
|
+
return resolve16(dbPath);
|
|
32689
33232
|
if (process.env["TODOS_DB_PATH"] && process.env["TODOS_DB_PATH"] !== ":memory:") {
|
|
32690
|
-
return
|
|
33233
|
+
return resolve16(process.env["TODOS_DB_PATH"]);
|
|
32691
33234
|
}
|
|
32692
33235
|
const db = getDatabase();
|
|
32693
33236
|
const filename = db.filename;
|
|
@@ -32697,11 +33240,11 @@ function resolveDbPath(dbPath) {
|
|
|
32697
33240
|
}
|
|
32698
33241
|
function backupDatabase(outputPath, sourcePath) {
|
|
32699
33242
|
const source9 = resolveDbPath(sourcePath);
|
|
32700
|
-
if (!
|
|
33243
|
+
if (!existsSync21(source9))
|
|
32701
33244
|
throw new Error(`Database not found: ${source9}`);
|
|
32702
|
-
mkdirSync13(
|
|
33245
|
+
mkdirSync13(dirname12(outputPath), { recursive: true });
|
|
32703
33246
|
closeDatabase();
|
|
32704
|
-
const src = new
|
|
33247
|
+
const src = new Database4(source9);
|
|
32705
33248
|
try {
|
|
32706
33249
|
src.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
32707
33250
|
} catch {}
|
|
@@ -32709,7 +33252,7 @@ function backupDatabase(outputPath, sourcePath) {
|
|
|
32709
33252
|
src.close();
|
|
32710
33253
|
writeFileSync11(outputPath, image);
|
|
32711
33254
|
const method = "file_copy";
|
|
32712
|
-
const bytes =
|
|
33255
|
+
const bytes = statSync7(outputPath).size;
|
|
32713
33256
|
return {
|
|
32714
33257
|
schema_version: DB_BACKUP_SCHEMA,
|
|
32715
33258
|
source_path: source9,
|
|
@@ -32720,14 +33263,14 @@ function backupDatabase(outputPath, sourcePath) {
|
|
|
32720
33263
|
};
|
|
32721
33264
|
}
|
|
32722
33265
|
function restoreDatabase(backupPath, targetPath) {
|
|
32723
|
-
if (!
|
|
33266
|
+
if (!existsSync21(backupPath))
|
|
32724
33267
|
throw new Error(`Backup not found: ${backupPath}`);
|
|
32725
33268
|
const integrity = checkDatabaseIntegrity(backupPath);
|
|
32726
33269
|
if (!integrity.ok) {
|
|
32727
33270
|
throw new Error(`Backup failed integrity check: ${integrity.errors.join("; ")}`);
|
|
32728
33271
|
}
|
|
32729
|
-
const target = targetPath ?
|
|
32730
|
-
mkdirSync13(
|
|
33272
|
+
const target = targetPath ? resolve16(targetPath) : resolveDbPath();
|
|
33273
|
+
mkdirSync13(dirname12(target), { recursive: true });
|
|
32731
33274
|
const staging = `${target}.restore.tmp`;
|
|
32732
33275
|
copyFileSync(backupPath, staging);
|
|
32733
33276
|
copyFileSync(staging, target);
|
|
@@ -32739,15 +33282,15 @@ function restoreDatabase(backupPath, targetPath) {
|
|
|
32739
33282
|
schema_version: DB_BACKUP_SCHEMA,
|
|
32740
33283
|
source_path: backupPath,
|
|
32741
33284
|
backup_path: target,
|
|
32742
|
-
bytes:
|
|
33285
|
+
bytes: statSync7(target).size,
|
|
32743
33286
|
method: "file_copy",
|
|
32744
33287
|
created_at: new Date().toISOString()
|
|
32745
33288
|
};
|
|
32746
33289
|
}
|
|
32747
33290
|
function checkDatabaseIntegrity(dbPath) {
|
|
32748
|
-
const path = dbPath ?
|
|
33291
|
+
const path = dbPath ? resolve16(dbPath) : resolveDbPath();
|
|
32749
33292
|
const errors = [];
|
|
32750
|
-
if (!
|
|
33293
|
+
if (!existsSync21(path)) {
|
|
32751
33294
|
return {
|
|
32752
33295
|
schema_version: DB_BACKUP_SCHEMA,
|
|
32753
33296
|
path,
|
|
@@ -32760,7 +33303,7 @@ function checkDatabaseIntegrity(dbPath) {
|
|
|
32760
33303
|
}
|
|
32761
33304
|
let db;
|
|
32762
33305
|
try {
|
|
32763
|
-
db = new
|
|
33306
|
+
db = new Database4(path, { readonly: true });
|
|
32764
33307
|
} catch (e) {
|
|
32765
33308
|
return {
|
|
32766
33309
|
schema_version: DB_BACKUP_SCHEMA,
|
|
@@ -32810,18 +33353,18 @@ function checkDatabaseIntegrity(dbPath) {
|
|
|
32810
33353
|
};
|
|
32811
33354
|
}
|
|
32812
33355
|
function compactDatabase(dbPath) {
|
|
32813
|
-
const path = dbPath ?
|
|
32814
|
-
const before =
|
|
32815
|
-
const db = new
|
|
33356
|
+
const path = dbPath ? resolve16(dbPath) : resolveDbPath();
|
|
33357
|
+
const before = statSync7(path).size;
|
|
33358
|
+
const db = new Database4(path);
|
|
32816
33359
|
db.exec("VACUUM");
|
|
32817
33360
|
db.close();
|
|
32818
|
-
const after =
|
|
33361
|
+
const after = statSync7(path).size;
|
|
32819
33362
|
closeDatabase();
|
|
32820
33363
|
return { path, bytes_before: before, bytes_after: after };
|
|
32821
33364
|
}
|
|
32822
33365
|
function migrationDryRun(dbPath) {
|
|
32823
|
-
const path = dbPath ?
|
|
32824
|
-
const db = new
|
|
33366
|
+
const path = dbPath ? resolve16(dbPath) : resolveDbPath();
|
|
33367
|
+
const db = new Database4(path, { readonly: true });
|
|
32825
33368
|
let current = 0;
|
|
32826
33369
|
try {
|
|
32827
33370
|
const row = db.query("SELECT MAX(id) as id FROM _migrations").get();
|
|
@@ -32844,13 +33387,13 @@ function migrationDryRun(dbPath) {
|
|
|
32844
33387
|
};
|
|
32845
33388
|
}
|
|
32846
33389
|
function defaultBackupPath(dbPath) {
|
|
32847
|
-
const base = dbPath ?
|
|
33390
|
+
const base = dbPath ? dirname12(resolve16(dbPath)) : dirname12(resolveDbPath());
|
|
32848
33391
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
32849
|
-
return
|
|
33392
|
+
return join17(base, "backups", `todos-${stamp}.db`);
|
|
32850
33393
|
}
|
|
32851
33394
|
function readBackupManifest(backupPath) {
|
|
32852
33395
|
const manifestPath = `${backupPath}.json`;
|
|
32853
|
-
if (!
|
|
33396
|
+
if (!existsSync21(manifestPath))
|
|
32854
33397
|
return null;
|
|
32855
33398
|
try {
|
|
32856
33399
|
return JSON.parse(readFileSync17(manifestPath, "utf8"));
|
|
@@ -32863,7 +33406,7 @@ function writeBackupManifest(backupPath, result) {
|
|
|
32863
33406
|
writeFileSyncSafe(manifestPath, JSON.stringify(result, null, 2));
|
|
32864
33407
|
}
|
|
32865
33408
|
function writeFileSyncSafe(path, content) {
|
|
32866
|
-
mkdirSync13(
|
|
33409
|
+
mkdirSync13(dirname12(path), { recursive: true });
|
|
32867
33410
|
writeFileSync11(path, content);
|
|
32868
33411
|
}
|
|
32869
33412
|
// src/lib/json-schemas.ts
|
|
@@ -32927,6 +33470,7 @@ var JSON_SCHEMAS = {
|
|
|
32927
33470
|
plan: def("plan", "todos.plan.v1", "Plan", ["schema_version", "id", "name", "status", "created_at", "updated_at"], {
|
|
32928
33471
|
schema_version: { type: "string", enum: ["todos.plan.v1"] },
|
|
32929
33472
|
id: { type: "string" },
|
|
33473
|
+
slug: { type: ["string", "null"] },
|
|
32930
33474
|
project_id: { type: ["string", "null"] },
|
|
32931
33475
|
name: { type: "string" },
|
|
32932
33476
|
description: { type: ["string", "null"] },
|
|
@@ -33370,15 +33914,15 @@ ${SCHEMA_ENTITIES.map((e) => `- **${e}**: \`${JSON_SCHEMAS[e].schema_version}\``
|
|
|
33370
33914
|
}
|
|
33371
33915
|
function exportSchemasToDirectory(dir) {
|
|
33372
33916
|
const { mkdirSync: mkdirSync14, writeFileSync: writeFileSync12 } = __require("fs");
|
|
33373
|
-
const { join:
|
|
33917
|
+
const { join: join18 } = __require("path");
|
|
33374
33918
|
mkdirSync14(dir, { recursive: true });
|
|
33375
33919
|
const written = [];
|
|
33376
33920
|
for (const entity of SCHEMA_ENTITIES) {
|
|
33377
|
-
const path =
|
|
33921
|
+
const path = join18(dir, `${entity}.${JSON_SCHEMAS[entity].schema_version.replace(/\./g, "-")}.json`);
|
|
33378
33922
|
writeFileSync12(path, JSON.stringify(JSON_SCHEMAS[entity], null, 2));
|
|
33379
33923
|
written.push(path);
|
|
33380
33924
|
}
|
|
33381
|
-
const catalogPath =
|
|
33925
|
+
const catalogPath = join18(dir, "catalog.json");
|
|
33382
33926
|
writeFileSync12(catalogPath, JSON.stringify({
|
|
33383
33927
|
catalog_version: JSON_SCHEMA_CATALOG_VERSION,
|
|
33384
33928
|
semver: SCHEMA_SEMVER,
|
|
@@ -34279,7 +34823,7 @@ function getReminderDocs() {
|
|
|
34279
34823
|
// src/lib/import-export-bridge.ts
|
|
34280
34824
|
init_database();
|
|
34281
34825
|
import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync14 } from "fs";
|
|
34282
|
-
import { dirname as
|
|
34826
|
+
import { dirname as dirname13 } from "path";
|
|
34283
34827
|
init_secret_redaction();
|
|
34284
34828
|
var BUNDLE_SCHEMA = "todos.bundle.v1";
|
|
34285
34829
|
var BUNDLE_TYPES = ["full_export", "tasks", "partial"];
|
|
@@ -34687,7 +35231,7 @@ function importBundle(bundle, options = {}, db) {
|
|
|
34687
35231
|
return result;
|
|
34688
35232
|
}
|
|
34689
35233
|
function writeBundleFile(bundle, path) {
|
|
34690
|
-
mkdirSync14(
|
|
35234
|
+
mkdirSync14(dirname13(path), { recursive: true });
|
|
34691
35235
|
writeFileSync12(path, JSON.stringify(bundle, null, 2), "utf8");
|
|
34692
35236
|
}
|
|
34693
35237
|
function readBundleFile(path) {
|
|
@@ -35168,7 +35712,7 @@ function createPlanWithSteps(name, steps, opts = {}, db) {
|
|
|
35168
35712
|
// src/lib/handoff-packets.ts
|
|
35169
35713
|
init_database();
|
|
35170
35714
|
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync15 } from "fs";
|
|
35171
|
-
import { dirname as
|
|
35715
|
+
import { dirname as dirname14 } from "path";
|
|
35172
35716
|
var HANDOFF_PACKET_SCHEMA = "todos.handoff_packet.v1";
|
|
35173
35717
|
function summarizeTask2(t) {
|
|
35174
35718
|
return {
|
|
@@ -35342,7 +35886,7 @@ function formatHandoffPacket(packet, format = "json") {
|
|
|
35342
35886
|
function exportHandoffPacket(input = {}, path, db) {
|
|
35343
35887
|
const packet = createHandoffPacket(input, db);
|
|
35344
35888
|
if (path) {
|
|
35345
|
-
mkdirSync15(
|
|
35889
|
+
mkdirSync15(dirname14(path), { recursive: true });
|
|
35346
35890
|
writeFileSync13(path, formatHandoffPacket(packet, "json"), "utf8");
|
|
35347
35891
|
}
|
|
35348
35892
|
return packet;
|
|
@@ -35947,7 +36491,7 @@ function generateCliReferenceMarkdown() {
|
|
|
35947
36491
|
// src/db/builtin-templates.ts
|
|
35948
36492
|
init_database();
|
|
35949
36493
|
import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
|
|
35950
|
-
import { join as
|
|
36494
|
+
import { join as join18 } from "path";
|
|
35951
36495
|
var BUILTIN_TEMPLATE_LIBRARY_VERSION = "2026-05-21";
|
|
35952
36496
|
var BUILTIN_TEMPLATE_LIBRARY_SOURCE = "bundled-local-template-library";
|
|
35953
36497
|
var TEMPLATE_LIBRARY_SCHEMA = "todos.template_library.v1";
|
|
@@ -36223,7 +36767,7 @@ function writeBuiltinTemplateFiles(directory) {
|
|
|
36223
36767
|
mkdirSync16(directory, { recursive: true });
|
|
36224
36768
|
const files = [];
|
|
36225
36769
|
for (const entry2 of exportBuiltinTemplateFiles()) {
|
|
36226
|
-
const path =
|
|
36770
|
+
const path = join18(directory, entry2.filename);
|
|
36227
36771
|
writeFileSync14(path, `${JSON.stringify(entry2.template, null, 2)}
|
|
36228
36772
|
`, "utf-8");
|
|
36229
36773
|
files.push(path);
|
|
@@ -36268,7 +36812,7 @@ function initBuiltinTemplates(db) {
|
|
|
36268
36812
|
// src/lib/template-library.ts
|
|
36269
36813
|
init_database();
|
|
36270
36814
|
import { writeFileSync as writeFileSync15, readFileSync as readFileSync19, mkdirSync as mkdirSync17 } from "fs";
|
|
36271
|
-
import { dirname as
|
|
36815
|
+
import { dirname as dirname15 } from "path";
|
|
36272
36816
|
function listTemplateLibrary(db) {
|
|
36273
36817
|
const d = db || getDatabase();
|
|
36274
36818
|
const installed = new Set(listTemplates(d).map((t) => t.name));
|
|
@@ -36310,7 +36854,7 @@ function exportTemplateLibraryCatalog(path, db) {
|
|
|
36310
36854
|
templates: listTemplateLibrary(db)
|
|
36311
36855
|
};
|
|
36312
36856
|
if (path) {
|
|
36313
|
-
mkdirSync17(
|
|
36857
|
+
mkdirSync17(dirname15(path), { recursive: true });
|
|
36314
36858
|
writeFileSync15(path, JSON.stringify(catalog, null, 2), "utf8");
|
|
36315
36859
|
}
|
|
36316
36860
|
return catalog;
|
|
@@ -36370,11 +36914,11 @@ init_database();
|
|
|
36370
36914
|
init_machines();
|
|
36371
36915
|
import { hostname as hostname2 } from "os";
|
|
36372
36916
|
var MACHINE_TOPOLOGY_SCHEMA = "todos.machine_topology.v1";
|
|
36373
|
-
function
|
|
36917
|
+
function normalizePath5(p) {
|
|
36374
36918
|
return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
36375
36919
|
}
|
|
36376
36920
|
function detectCasingMismatch(stored, canonical) {
|
|
36377
|
-
return
|
|
36921
|
+
return normalizePath5(stored) === normalizePath5(canonical) && stored !== canonical;
|
|
36378
36922
|
}
|
|
36379
36923
|
function registerLocalMachine(db) {
|
|
36380
36924
|
resetMachineId();
|
|
@@ -36393,7 +36937,7 @@ function getPathOverrides(db) {
|
|
|
36393
36937
|
project_name: project?.name ?? row.project_id.slice(0, 8),
|
|
36394
36938
|
machine_id: row.machine_id,
|
|
36395
36939
|
path: row.path,
|
|
36396
|
-
path_normalized:
|
|
36940
|
+
path_normalized: normalizePath5(row.path),
|
|
36397
36941
|
casing_mismatch: detectCasingMismatch(row.path, canonical)
|
|
36398
36942
|
};
|
|
36399
36943
|
});
|
|
@@ -36505,10 +37049,10 @@ todos machines topology # full diagnostic report
|
|
|
36505
37049
|
`;
|
|
36506
37050
|
}
|
|
36507
37051
|
// src/lib/environment-snapshots.ts
|
|
36508
|
-
import { createHash as
|
|
36509
|
-
import { existsSync as
|
|
37052
|
+
import { createHash as createHash13 } from "crypto";
|
|
37053
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20, statSync as statSync8 } from "fs";
|
|
36510
37054
|
import { hostname as hostname3, platform, arch } from "os";
|
|
36511
|
-
import { dirname as
|
|
37055
|
+
import { dirname as dirname16, join as join19, resolve as resolve17 } from "path";
|
|
36512
37056
|
import { tmpdir as tmpdir3 } from "os";
|
|
36513
37057
|
init_database();
|
|
36514
37058
|
init_redaction();
|
|
@@ -36530,13 +37074,13 @@ var CONFIG_FILES = [
|
|
|
36530
37074
|
"dashboard/vite.config.ts"
|
|
36531
37075
|
];
|
|
36532
37076
|
function sha2565(value) {
|
|
36533
|
-
return
|
|
37077
|
+
return createHash13("sha256").update(value).digest("hex");
|
|
36534
37078
|
}
|
|
36535
37079
|
function fileRecord(root, relativePath) {
|
|
36536
|
-
const path =
|
|
36537
|
-
if (!
|
|
37080
|
+
const path = join19(root, relativePath);
|
|
37081
|
+
if (!existsSync22(path))
|
|
36538
37082
|
return null;
|
|
36539
|
-
const stat =
|
|
37083
|
+
const stat = statSync8(path);
|
|
36540
37084
|
if (!stat.isFile())
|
|
36541
37085
|
return null;
|
|
36542
37086
|
const content = readFileSync20(path);
|
|
@@ -36546,7 +37090,7 @@ function manifestRecord(root, relativePath) {
|
|
|
36546
37090
|
const base = fileRecord(root, relativePath);
|
|
36547
37091
|
if (!base)
|
|
36548
37092
|
return null;
|
|
36549
|
-
const parsed = readJsonFile(
|
|
37093
|
+
const parsed = readJsonFile(join19(root, relativePath));
|
|
36550
37094
|
if (!parsed)
|
|
36551
37095
|
return { ...base, redacted: {} };
|
|
36552
37096
|
const redacted = redactValue({
|
|
@@ -36641,15 +37185,15 @@ function commandEnv(env, includeValues) {
|
|
|
36641
37185
|
function defaultSnapshotDir() {
|
|
36642
37186
|
const dbPath = getDatabasePath();
|
|
36643
37187
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
36644
|
-
return
|
|
36645
|
-
return
|
|
37188
|
+
return join19(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
37189
|
+
return join19(dirname16(resolve17(dbPath)), "environment-snapshots");
|
|
36646
37190
|
}
|
|
36647
37191
|
function snapshotWithId(snapshot) {
|
|
36648
37192
|
const digest = sha2565(JSON.stringify(snapshot)).slice(0, 24);
|
|
36649
37193
|
return { id: `env_${digest}`, ...snapshot };
|
|
36650
37194
|
}
|
|
36651
37195
|
function captureEnvironmentSnapshot(input = {}) {
|
|
36652
|
-
const root =
|
|
37196
|
+
const root = resolve17(input.root || process.cwd());
|
|
36653
37197
|
const env = input.env || process.env;
|
|
36654
37198
|
const warnings = [];
|
|
36655
37199
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -36689,13 +37233,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
36689
37233
|
});
|
|
36690
37234
|
}
|
|
36691
37235
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
36692
|
-
const path = outputPath ?
|
|
36693
|
-
ensureDir2(
|
|
37236
|
+
const path = outputPath ? resolve17(outputPath) : join19(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
37237
|
+
ensureDir2(dirname16(path));
|
|
36694
37238
|
writeJsonFile(path, snapshot);
|
|
36695
37239
|
return path;
|
|
36696
37240
|
}
|
|
36697
37241
|
function readEnvironmentSnapshot(path) {
|
|
36698
|
-
const snapshot = readJsonFile(
|
|
37242
|
+
const snapshot = readJsonFile(resolve17(path));
|
|
36699
37243
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
36700
37244
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
36701
37245
|
}
|
|
@@ -36780,9 +37324,9 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
|
|
|
36780
37324
|
}
|
|
36781
37325
|
// src/lib/decision-records.ts
|
|
36782
37326
|
init_database();
|
|
36783
|
-
import { createHash as
|
|
37327
|
+
import { createHash as createHash14 } from "crypto";
|
|
36784
37328
|
import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
|
|
36785
|
-
import { dirname as
|
|
37329
|
+
import { dirname as dirname17, join as join20 } from "path";
|
|
36786
37330
|
var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
|
|
36787
37331
|
var KNOWLEDGE_SNAPSHOT_SCHEMA = "todos.knowledge_snapshot.v1";
|
|
36788
37332
|
var DECISION_STATUSES = ["proposed", "accepted", "deprecated", "superseded", "rejected"];
|
|
@@ -36836,7 +37380,7 @@ function rowToDecisionRecord(row) {
|
|
|
36836
37380
|
}
|
|
36837
37381
|
function stableSnapshotHash(payload) {
|
|
36838
37382
|
const { captured_at: _capturedAt, ...rest } = payload;
|
|
36839
|
-
return
|
|
37383
|
+
return createHash14("sha256").update(JSON.stringify(rest)).digest("hex");
|
|
36840
37384
|
}
|
|
36841
37385
|
function createDecisionRecord(input, db) {
|
|
36842
37386
|
const d = db || getDatabase();
|
|
@@ -37012,8 +37556,8 @@ function exportDecisionRecord(id, outputPath, format = "markdown", db) {
|
|
|
37012
37556
|
if (!record)
|
|
37013
37557
|
throw new Error(`Decision record not found: ${id}`);
|
|
37014
37558
|
const content = format === "markdown" ? formatDecisionRecordMarkdown(record) : JSON.stringify(record, null, 2);
|
|
37015
|
-
const path = outputPath ??
|
|
37016
|
-
mkdirSync18(
|
|
37559
|
+
const path = outputPath ?? join20(process.cwd(), ".todos", "decisions", `${record.short_ref}.${format === "markdown" ? "md" : "json"}`);
|
|
37560
|
+
mkdirSync18(dirname17(path), { recursive: true });
|
|
37017
37561
|
writeFileSync16(path, content, "utf8");
|
|
37018
37562
|
return { path, content };
|
|
37019
37563
|
}
|
|
@@ -37160,8 +37704,8 @@ function exportKnowledgeSnapshot(id, outputPath, format = "markdown", db) {
|
|
|
37160
37704
|
throw new Error(`Knowledge snapshot not found: ${id}`);
|
|
37161
37705
|
const content = format === "markdown" ? formatKnowledgeSnapshotMarkdown(record) : JSON.stringify(record, null, 2);
|
|
37162
37706
|
const slug = record.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
37163
|
-
const path = outputPath ??
|
|
37164
|
-
mkdirSync18(
|
|
37707
|
+
const path = outputPath ?? join20(process.cwd(), ".todos", "knowledge", `${slug || record.id.slice(0, 8)}.${format === "markdown" ? "md" : "json"}`);
|
|
37708
|
+
mkdirSync18(dirname17(path), { recursive: true });
|
|
37165
37709
|
writeFileSync16(path, content, "utf8");
|
|
37166
37710
|
return { path, content };
|
|
37167
37711
|
}
|
|
@@ -37189,7 +37733,7 @@ Schema versions:
|
|
|
37189
37733
|
// src/lib/report-exports.ts
|
|
37190
37734
|
init_database();
|
|
37191
37735
|
import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
|
|
37192
|
-
import { dirname as
|
|
37736
|
+
import { dirname as dirname18 } from "path";
|
|
37193
37737
|
init_secret_redaction();
|
|
37194
37738
|
var REPORT_EXPORT_SCHEMA = "todos.report_export.v1";
|
|
37195
37739
|
var REPORT_KINDS = ["project", "plan", "run", "evidence", "roadmap", "retrospective"];
|
|
@@ -37421,7 +37965,7 @@ function formatReportExport(data, format) {
|
|
|
37421
37965
|
return format === "html" ? formatReportHtml(data) : formatReportMarkdown(data);
|
|
37422
37966
|
}
|
|
37423
37967
|
function writeReportExport(data, format, path) {
|
|
37424
|
-
mkdirSync19(
|
|
37968
|
+
mkdirSync19(dirname18(path), { recursive: true });
|
|
37425
37969
|
writeFileSync17(path, formatReportExport(data, format), "utf8");
|
|
37426
37970
|
}
|
|
37427
37971
|
function exportReport(input, db) {
|
|
@@ -37455,8 +37999,8 @@ todos report export --kind retrospective --days 14 --format markdown --out retro
|
|
|
37455
37999
|
`;
|
|
37456
38000
|
}
|
|
37457
38001
|
// src/lib/command-aliases.ts
|
|
37458
|
-
import { existsSync as
|
|
37459
|
-
import { join as
|
|
38002
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21, writeFileSync as writeFileSync18, mkdirSync as mkdirSync20 } from "fs";
|
|
38003
|
+
import { join as join21 } from "path";
|
|
37460
38004
|
var COMMAND_ALIASES_SCHEMA = "todos.command_aliases.v1";
|
|
37461
38005
|
var RESERVED = new Set([...listTopLevelCommands(), "help", "version", "alias", "shortcuts"]);
|
|
37462
38006
|
var BUILTIN_SHORTCUTS = [
|
|
@@ -37475,7 +38019,7 @@ var BUILTIN_SHORTCUTS = [
|
|
|
37475
38019
|
{ pattern: /^reports?$/, argv: ["report", "docs"], explain: "Report export documentation" }
|
|
37476
38020
|
];
|
|
37477
38021
|
function aliasesPath(cwd = process.cwd()) {
|
|
37478
|
-
return
|
|
38022
|
+
return join21(cwd, ".todos", "aliases.json");
|
|
37479
38023
|
}
|
|
37480
38024
|
function emptyStore() {
|
|
37481
38025
|
return { schema_version: COMMAND_ALIASES_SCHEMA, aliases: {}, updated_at: new Date(0).toISOString() };
|
|
@@ -37492,7 +38036,7 @@ function validateAliasName(name) {
|
|
|
37492
38036
|
}
|
|
37493
38037
|
function loadAliasStore(cwd) {
|
|
37494
38038
|
const path = aliasesPath(cwd);
|
|
37495
|
-
if (!
|
|
38039
|
+
if (!existsSync23(path))
|
|
37496
38040
|
return emptyStore();
|
|
37497
38041
|
const parsed = JSON.parse(readFileSync21(path, "utf8"));
|
|
37498
38042
|
if (parsed.schema_version !== COMMAND_ALIASES_SCHEMA) {
|
|
@@ -37502,7 +38046,7 @@ function loadAliasStore(cwd) {
|
|
|
37502
38046
|
}
|
|
37503
38047
|
function saveAliasStore(store, cwd) {
|
|
37504
38048
|
const path = aliasesPath(cwd);
|
|
37505
|
-
mkdirSync20(
|
|
38049
|
+
mkdirSync20(join21(path, ".."), { recursive: true });
|
|
37506
38050
|
store.updated_at = new Date().toISOString();
|
|
37507
38051
|
writeFileSync18(path, JSON.stringify(store, null, 2), "utf8");
|
|
37508
38052
|
}
|
|
@@ -38017,7 +38561,7 @@ function normalizeBranch(value) {
|
|
|
38017
38561
|
}
|
|
38018
38562
|
return branch;
|
|
38019
38563
|
}
|
|
38020
|
-
function
|
|
38564
|
+
function normalizePath6(value) {
|
|
38021
38565
|
const path = value.trim().replace(/\\/g, "/");
|
|
38022
38566
|
if (!path || path.startsWith("/") || path.includes("\x00"))
|
|
38023
38567
|
return null;
|
|
@@ -38049,7 +38593,7 @@ function getGitStatus(root, branch, includeGitStatus) {
|
|
|
38049
38593
|
const branchExists = runGit3(root, ["show-ref", "--verify", `refs/heads/${branch}`]) !== null;
|
|
38050
38594
|
const status = runGit3(root, ["status", "--short"]) || "";
|
|
38051
38595
|
const dirtyFiles = status.split(`
|
|
38052
|
-
`).map((line) => line.trim()).filter(Boolean).map((line) =>
|
|
38596
|
+
`).map((line) => line.trim()).filter(Boolean).map((line) => normalizePath6(line.replace(/^.. /, "").replace(/^.* -> /, ""))).filter((path) => Boolean(path));
|
|
38053
38597
|
return { has_git: true, current_branch: currentBranch || null, branch_exists: branchExists, dirty_files: dirtyFiles };
|
|
38054
38598
|
}
|
|
38055
38599
|
function resolveScope2(input, db) {
|
|
@@ -38064,8 +38608,8 @@ function resolveScope2(input, db) {
|
|
|
38064
38608
|
throw new Error("task_id or plan_id is required");
|
|
38065
38609
|
}
|
|
38066
38610
|
function collectPlannedFiles(tasks, explicitPaths, db) {
|
|
38067
|
-
const fromTasks = tasks.flatMap((task2) => listTaskFiles(task2.id, db).map((file) =>
|
|
38068
|
-
const fromInput = (explicitPaths || []).map(
|
|
38611
|
+
const fromTasks = tasks.flatMap((task2) => listTaskFiles(task2.id, db).map((file) => normalizePath6(file.path)));
|
|
38612
|
+
const fromInput = (explicitPaths || []).map(normalizePath6);
|
|
38069
38613
|
return uniqueSorted2([...fromTasks, ...fromInput]);
|
|
38070
38614
|
}
|
|
38071
38615
|
function detectBranchPlanConflicts(taskIds, files, db) {
|
|
@@ -38150,18 +38694,18 @@ function createBranchWorkPlan(input, db) {
|
|
|
38150
38694
|
}
|
|
38151
38695
|
// src/lib/user-scaffolds.ts
|
|
38152
38696
|
init_database();
|
|
38153
|
-
import { existsSync as
|
|
38154
|
-
import { join as
|
|
38697
|
+
import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync19, mkdirSync as mkdirSync21 } from "fs";
|
|
38698
|
+
import { join as join22 } from "path";
|
|
38155
38699
|
var USER_SCAFFOLD_SCHEMA = "todos.user_scaffold.v1";
|
|
38156
38700
|
var SCAFFOLD_KINDS = ["task", "project", "plan", "checklist", "contract", "verification_policy"];
|
|
38157
38701
|
function storeDir(cwd = process.cwd()) {
|
|
38158
|
-
return
|
|
38702
|
+
return join22(cwd, ".todos", "scaffolds");
|
|
38159
38703
|
}
|
|
38160
38704
|
function storePath(cwd) {
|
|
38161
|
-
return
|
|
38705
|
+
return join22(storeDir(cwd), "store.json");
|
|
38162
38706
|
}
|
|
38163
38707
|
function versionsDir(cwd) {
|
|
38164
|
-
return
|
|
38708
|
+
return join22(storeDir(cwd), "versions");
|
|
38165
38709
|
}
|
|
38166
38710
|
function slugify5(name) {
|
|
38167
38711
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -38171,7 +38715,7 @@ function emptyStore2() {
|
|
|
38171
38715
|
}
|
|
38172
38716
|
function loadUserScaffoldStore(cwd) {
|
|
38173
38717
|
const path = storePath(cwd);
|
|
38174
|
-
if (!
|
|
38718
|
+
if (!existsSync24(path))
|
|
38175
38719
|
return emptyStore2();
|
|
38176
38720
|
const parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
38177
38721
|
if (parsed.schema_version !== USER_SCAFFOLD_SCHEMA) {
|
|
@@ -38186,7 +38730,7 @@ function saveUserScaffoldStore(store, cwd) {
|
|
|
38186
38730
|
}
|
|
38187
38731
|
function snapshotVersion(scaffold, cwd) {
|
|
38188
38732
|
mkdirSync21(versionsDir(cwd), { recursive: true });
|
|
38189
|
-
const path =
|
|
38733
|
+
const path = join22(versionsDir(cwd), `${scaffold.id}-v${scaffold.version}.json`);
|
|
38190
38734
|
writeFileSync19(path, JSON.stringify(scaffold, null, 2), "utf8");
|
|
38191
38735
|
}
|
|
38192
38736
|
function listUserScaffolds(kind, cwd) {
|
|
@@ -38433,7 +38977,7 @@ function listLinkedTemplates(db, cwd) {
|
|
|
38433
38977
|
// src/lib/agent-workflow-demo.ts
|
|
38434
38978
|
init_database();
|
|
38435
38979
|
import { mkdtempSync } from "fs";
|
|
38436
|
-
import { join as
|
|
38980
|
+
import { join as join23 } from "path";
|
|
38437
38981
|
import { tmpdir as tmpdir4 } from "os";
|
|
38438
38982
|
var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
|
|
38439
38983
|
var DEMO_DEFAULT_AGENT = "demoagent";
|
|
@@ -38449,7 +38993,7 @@ function setupEphemeralDemoDb(options = {}) {
|
|
|
38449
38993
|
if (options.db_path) {
|
|
38450
38994
|
db_path = options.db_path;
|
|
38451
38995
|
} else if (options.persist) {
|
|
38452
|
-
db_path =
|
|
38996
|
+
db_path = join23(mkdtempSync(join23(tmpdir4(), "todos-demo-")), "todos.db");
|
|
38453
38997
|
} else {
|
|
38454
38998
|
db_path = ":memory:";
|
|
38455
38999
|
}
|
|
@@ -40774,18 +41318,18 @@ function runSearchView(idOrName, db) {
|
|
|
40774
41318
|
return { ...runSavedSearch(view.filters, view.scope, d), view };
|
|
40775
41319
|
}
|
|
40776
41320
|
// src/lib/claude-tasks.ts
|
|
40777
|
-
import { existsSync as
|
|
40778
|
-
import { join as
|
|
41321
|
+
import { existsSync as existsSync25, readFileSync as readFileSync23, readdirSync as readdirSync5, writeFileSync as writeFileSync20 } from "fs";
|
|
41322
|
+
import { join as join24 } from "path";
|
|
40779
41323
|
init_config();
|
|
40780
41324
|
init_sync_utils();
|
|
40781
41325
|
function getTaskListDir(taskListId) {
|
|
40782
|
-
return
|
|
41326
|
+
return join24(HOME, ".claude", "tasks", taskListId);
|
|
40783
41327
|
}
|
|
40784
41328
|
function readClaudeTask(dir, filename) {
|
|
40785
|
-
return readJsonFile(
|
|
41329
|
+
return readJsonFile(join24(dir, filename));
|
|
40786
41330
|
}
|
|
40787
41331
|
function writeClaudeTask(dir, task2) {
|
|
40788
|
-
writeJsonFile(
|
|
41332
|
+
writeJsonFile(join24(dir, `${task2.id}.json`), task2);
|
|
40789
41333
|
}
|
|
40790
41334
|
function toClaudeStatus(status) {
|
|
40791
41335
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -40797,14 +41341,14 @@ function toSqliteStatus(status) {
|
|
|
40797
41341
|
return status;
|
|
40798
41342
|
}
|
|
40799
41343
|
function readPrefixCounter(dir) {
|
|
40800
|
-
const path =
|
|
40801
|
-
if (!
|
|
41344
|
+
const path = join24(dir, ".prefix-counter");
|
|
41345
|
+
if (!existsSync25(path))
|
|
40802
41346
|
return 0;
|
|
40803
41347
|
const val = parseInt(readFileSync23(path, "utf-8").trim(), 10);
|
|
40804
41348
|
return isNaN(val) ? 0 : val;
|
|
40805
41349
|
}
|
|
40806
41350
|
function writePrefixCounter(dir, value) {
|
|
40807
|
-
writeFileSync20(
|
|
41351
|
+
writeFileSync20(join24(dir, ".prefix-counter"), String(value));
|
|
40808
41352
|
}
|
|
40809
41353
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
40810
41354
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -40831,7 +41375,7 @@ function taskToClaudeTask(task2, claudeTaskId, existingMeta) {
|
|
|
40831
41375
|
}
|
|
40832
41376
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
40833
41377
|
const dir = getTaskListDir(taskListId);
|
|
40834
|
-
if (!
|
|
41378
|
+
if (!existsSync25(dir))
|
|
40835
41379
|
ensureDir2(dir);
|
|
40836
41380
|
const filter = {};
|
|
40837
41381
|
if (projectId)
|
|
@@ -40840,7 +41384,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
40840
41384
|
const existingByTodosId = new Map;
|
|
40841
41385
|
const files = listJsonFiles(dir);
|
|
40842
41386
|
for (const f of files) {
|
|
40843
|
-
const path =
|
|
41387
|
+
const path = join24(dir, f);
|
|
40844
41388
|
const ct = readClaudeTask(dir, f);
|
|
40845
41389
|
if (ct?.metadata?.["todos_id"]) {
|
|
40846
41390
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -40927,10 +41471,10 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
40927
41471
|
}
|
|
40928
41472
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
40929
41473
|
const dir = getTaskListDir(taskListId);
|
|
40930
|
-
if (!
|
|
41474
|
+
if (!existsSync25(dir)) {
|
|
40931
41475
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
40932
41476
|
}
|
|
40933
|
-
const files =
|
|
41477
|
+
const files = readdirSync5(dir).filter((f) => f.endsWith(".json"));
|
|
40934
41478
|
let pulled = 0;
|
|
40935
41479
|
const errors = [];
|
|
40936
41480
|
const prefer = options.prefer || "remote";
|
|
@@ -40947,7 +41491,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
40947
41491
|
}
|
|
40948
41492
|
for (const f of files) {
|
|
40949
41493
|
try {
|
|
40950
|
-
const filePath =
|
|
41494
|
+
const filePath = join24(dir, f);
|
|
40951
41495
|
const ct = readClaudeTask(dir, f);
|
|
40952
41496
|
if (!ct)
|
|
40953
41497
|
continue;
|
|
@@ -41015,22 +41559,22 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
41015
41559
|
}
|
|
41016
41560
|
|
|
41017
41561
|
// src/lib/agent-tasks.ts
|
|
41018
|
-
import { existsSync as
|
|
41019
|
-
import { join as
|
|
41562
|
+
import { existsSync as existsSync26 } from "fs";
|
|
41563
|
+
import { join as join25 } from "path";
|
|
41020
41564
|
init_sync_utils();
|
|
41021
41565
|
init_config();
|
|
41022
41566
|
function agentBaseDir(agent) {
|
|
41023
41567
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
41024
|
-
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] ||
|
|
41568
|
+
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join25(getTodosGlobalDir(), "agents");
|
|
41025
41569
|
}
|
|
41026
41570
|
function getTaskListDir2(agent, taskListId) {
|
|
41027
|
-
return
|
|
41571
|
+
return join25(agentBaseDir(agent), agent, taskListId);
|
|
41028
41572
|
}
|
|
41029
41573
|
function readAgentTask(dir, filename) {
|
|
41030
|
-
return readJsonFile(
|
|
41574
|
+
return readJsonFile(join25(dir, filename));
|
|
41031
41575
|
}
|
|
41032
41576
|
function writeAgentTask(dir, task2) {
|
|
41033
|
-
writeJsonFile(
|
|
41577
|
+
writeJsonFile(join25(dir, `${task2.id}.json`), task2);
|
|
41034
41578
|
}
|
|
41035
41579
|
function taskToAgentTask(task2, externalId, existingMeta) {
|
|
41036
41580
|
return {
|
|
@@ -41055,7 +41599,7 @@ function metadataKey(agent) {
|
|
|
41055
41599
|
}
|
|
41056
41600
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
41057
41601
|
const dir = getTaskListDir2(agent, taskListId);
|
|
41058
|
-
if (!
|
|
41602
|
+
if (!existsSync26(dir))
|
|
41059
41603
|
ensureDir2(dir);
|
|
41060
41604
|
const filter = {};
|
|
41061
41605
|
if (projectId)
|
|
@@ -41064,7 +41608,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
41064
41608
|
const existingByTodosId = new Map;
|
|
41065
41609
|
const files = listJsonFiles(dir);
|
|
41066
41610
|
for (const f of files) {
|
|
41067
|
-
const path =
|
|
41611
|
+
const path = join25(dir, f);
|
|
41068
41612
|
const at = readAgentTask(dir, f);
|
|
41069
41613
|
if (at?.metadata?.["todos_id"]) {
|
|
41070
41614
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -41138,7 +41682,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
41138
41682
|
}
|
|
41139
41683
|
function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
41140
41684
|
const dir = getTaskListDir2(agent, taskListId);
|
|
41141
|
-
if (!
|
|
41685
|
+
if (!existsSync26(dir)) {
|
|
41142
41686
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
41143
41687
|
}
|
|
41144
41688
|
const files = listJsonFiles(dir);
|
|
@@ -41157,7 +41701,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
41157
41701
|
}
|
|
41158
41702
|
for (const f of files) {
|
|
41159
41703
|
try {
|
|
41160
|
-
const filePath =
|
|
41704
|
+
const filePath = join25(dir, f);
|
|
41161
41705
|
const at = readAgentTask(dir, f);
|
|
41162
41706
|
if (!at)
|
|
41163
41707
|
continue;
|
|
@@ -41295,9 +41839,9 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
|
|
|
41295
41839
|
return { pushed, pulled, errors };
|
|
41296
41840
|
}
|
|
41297
41841
|
// src/lib/extract.ts
|
|
41298
|
-
import { existsSync as
|
|
41299
|
-
import { createHash as
|
|
41300
|
-
import { relative as relative6, resolve as
|
|
41842
|
+
import { existsSync as existsSync27, readFileSync as readFileSync24, statSync as statSync9 } from "fs";
|
|
41843
|
+
import { createHash as createHash15 } from "crypto";
|
|
41844
|
+
import { relative as relative6, resolve as resolve18, join as join26 } from "path";
|
|
41301
41845
|
var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
|
|
41302
41846
|
var DEFAULT_EXTENSIONS = new Set([
|
|
41303
41847
|
".ts",
|
|
@@ -41361,15 +41905,15 @@ var SKIP_DIRS2 = new Set([
|
|
|
41361
41905
|
".parcel-cache"
|
|
41362
41906
|
]);
|
|
41363
41907
|
function stableHash(value) {
|
|
41364
|
-
return
|
|
41908
|
+
return createHash15("sha256").update(value).digest("hex");
|
|
41365
41909
|
}
|
|
41366
41910
|
function normalizePathForMatch(value) {
|
|
41367
41911
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
41368
41912
|
}
|
|
41369
41913
|
function readGitignorePatterns(basePath) {
|
|
41370
|
-
const root =
|
|
41371
|
-
const gitignorePath =
|
|
41372
|
-
if (!
|
|
41914
|
+
const root = statSync9(basePath).isFile() ? resolve18(basePath, "..") : basePath;
|
|
41915
|
+
const gitignorePath = join26(root, ".gitignore");
|
|
41916
|
+
if (!existsSync27(gitignorePath))
|
|
41373
41917
|
return [];
|
|
41374
41918
|
try {
|
|
41375
41919
|
return readFileSync24(gitignorePath, "utf-8").split(`
|
|
@@ -41478,7 +42022,7 @@ function extractFromSource(source9, filePath, tags = [...EXTRACT_TAGS]) {
|
|
|
41478
42022
|
return results;
|
|
41479
42023
|
}
|
|
41480
42024
|
function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
41481
|
-
const stat =
|
|
42025
|
+
const stat = statSync9(basePath);
|
|
41482
42026
|
if (stat.isFile()) {
|
|
41483
42027
|
return [basePath];
|
|
41484
42028
|
}
|
|
@@ -41503,7 +42047,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
41503
42047
|
return files.sort();
|
|
41504
42048
|
}
|
|
41505
42049
|
function buildCodebaseIndex(options) {
|
|
41506
|
-
const basePath =
|
|
42050
|
+
const basePath = resolve18(options.path);
|
|
41507
42051
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
41508
42052
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
41509
42053
|
const excludes = options.exclude || [];
|
|
@@ -41511,10 +42055,10 @@ function buildCodebaseIndex(options) {
|
|
|
41511
42055
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
41512
42056
|
const indexed = [];
|
|
41513
42057
|
for (const file of files) {
|
|
41514
|
-
const fullPath =
|
|
42058
|
+
const fullPath = statSync9(basePath).isFile() ? basePath : join26(basePath, file);
|
|
41515
42059
|
try {
|
|
41516
42060
|
const source9 = readFileSync24(fullPath, "utf-8");
|
|
41517
|
-
const relPath =
|
|
42061
|
+
const relPath = statSync9(basePath).isFile() ? relative6(resolve18(basePath, ".."), fullPath) : file;
|
|
41518
42062
|
indexed.push({
|
|
41519
42063
|
file: relPath,
|
|
41520
42064
|
checksum: stableHash(source9).slice(0, 24),
|
|
@@ -41534,7 +42078,7 @@ function buildCodebaseIndex(options) {
|
|
|
41534
42078
|
};
|
|
41535
42079
|
}
|
|
41536
42080
|
function extractTodos(options, db) {
|
|
41537
|
-
const basePath =
|
|
42081
|
+
const basePath = resolve18(options.path);
|
|
41538
42082
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
41539
42083
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
41540
42084
|
const excludes = options.exclude || [];
|
|
@@ -41542,10 +42086,10 @@ function extractTodos(options, db) {
|
|
|
41542
42086
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
41543
42087
|
const allComments = [];
|
|
41544
42088
|
for (const file of files) {
|
|
41545
|
-
const fullPath =
|
|
42089
|
+
const fullPath = statSync9(basePath).isFile() ? basePath : join26(basePath, file);
|
|
41546
42090
|
try {
|
|
41547
42091
|
const source9 = readFileSync24(fullPath, "utf-8");
|
|
41548
|
-
const relPath =
|
|
42092
|
+
const relPath = statSync9(basePath).isFile() ? relative6(resolve18(basePath, ".."), fullPath) : file;
|
|
41549
42093
|
const comments = extractFromSource(source9, relPath, tags);
|
|
41550
42094
|
allComments.push(...comments);
|
|
41551
42095
|
} catch {}
|
|
@@ -41639,7 +42183,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
41639
42183
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
41640
42184
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
41641
42185
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
41642
|
-
const root =
|
|
42186
|
+
const root = resolve18(options.path);
|
|
41643
42187
|
const runs = [];
|
|
41644
42188
|
let previous = new Map;
|
|
41645
42189
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -42249,7 +42793,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
|
|
|
42249
42793
|
}
|
|
42250
42794
|
// src/lib/agent-replay-simulator.ts
|
|
42251
42795
|
init_redaction();
|
|
42252
|
-
import { createHash as
|
|
42796
|
+
import { createHash as createHash16 } from "crypto";
|
|
42253
42797
|
import { readFileSync as readFileSync25 } from "fs";
|
|
42254
42798
|
function isObject(value) {
|
|
42255
42799
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -42271,7 +42815,7 @@ function stable2(value) {
|
|
|
42271
42815
|
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable2(value[key])]));
|
|
42272
42816
|
}
|
|
42273
42817
|
function fingerprint3(value) {
|
|
42274
|
-
return
|
|
42818
|
+
return createHash16("sha256").update(JSON.stringify(stable2(value))).digest("hex");
|
|
42275
42819
|
}
|
|
42276
42820
|
function unpackFixture(input) {
|
|
42277
42821
|
if (!isObject(input))
|
|
@@ -42510,9 +43054,9 @@ function renderAgentReplaySimulationMarkdown(simulation) {
|
|
|
42510
43054
|
}
|
|
42511
43055
|
// src/lib/local-extensions.ts
|
|
42512
43056
|
init_config();
|
|
42513
|
-
import { createHash as
|
|
42514
|
-
import { existsSync as
|
|
42515
|
-
import { basename as
|
|
43057
|
+
import { createHash as createHash17, createVerify } from "crypto";
|
|
43058
|
+
import { existsSync as existsSync28, readdirSync as readdirSync6, readFileSync as readFileSync26, statSync as statSync10 } from "fs";
|
|
43059
|
+
import { basename as basename6, join as join27, resolve as resolve19 } from "path";
|
|
42516
43060
|
init_redaction();
|
|
42517
43061
|
function isObject2(value) {
|
|
42518
43062
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -42597,7 +43141,7 @@ function parseJson(path) {
|
|
|
42597
43141
|
return JSON.parse(readFileSync26(path, "utf8"));
|
|
42598
43142
|
}
|
|
42599
43143
|
function sha2566(bytes) {
|
|
42600
|
-
return `sha256:${
|
|
43144
|
+
return `sha256:${createHash17("sha256").update(bytes).digest("hex")}`;
|
|
42601
43145
|
}
|
|
42602
43146
|
function compareVersions(a, b) {
|
|
42603
43147
|
const left = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -42792,11 +43336,11 @@ function verifyExtensionSignature(input) {
|
|
|
42792
43336
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
42793
43337
|
}
|
|
42794
43338
|
function inspectExtensionSource(source9) {
|
|
42795
|
-
const resolved =
|
|
42796
|
-
if (!
|
|
43339
|
+
const resolved = resolve19(source9);
|
|
43340
|
+
if (!existsSync28(resolved))
|
|
42797
43341
|
throw new Error(`extension source not found: ${source9}`);
|
|
42798
|
-
const stat =
|
|
42799
|
-
const manifestPath = stat.isDirectory() ? [
|
|
43342
|
+
const stat = statSync10(resolved);
|
|
43343
|
+
const manifestPath = stat.isDirectory() ? [join27(resolved, "todos.extension.json"), join27(resolved, "extension.json")].find(existsSync28) : resolved;
|
|
42800
43344
|
if (!manifestPath)
|
|
42801
43345
|
throw new Error(`extension directory ${source9} is missing todos.extension.json`);
|
|
42802
43346
|
const raw = readFileSync26(manifestPath);
|
|
@@ -42890,26 +43434,26 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
42890
43434
|
function projectExtensionSources(projectPath) {
|
|
42891
43435
|
if (!projectPath)
|
|
42892
43436
|
return [];
|
|
42893
|
-
const root =
|
|
43437
|
+
const root = resolve19(projectPath);
|
|
42894
43438
|
const candidates = [
|
|
42895
|
-
|
|
42896
|
-
|
|
43439
|
+
join27(root, "todos.extension.json"),
|
|
43440
|
+
join27(root, ".todos", "todos.extension.json")
|
|
42897
43441
|
];
|
|
42898
|
-
const extensionDir =
|
|
42899
|
-
if (
|
|
42900
|
-
for (const entry2 of
|
|
43442
|
+
const extensionDir = join27(root, ".todos", "extensions");
|
|
43443
|
+
if (existsSync28(extensionDir)) {
|
|
43444
|
+
for (const entry2 of readdirSync6(extensionDir)) {
|
|
42901
43445
|
if (entry2.startsWith("."))
|
|
42902
43446
|
continue;
|
|
42903
|
-
const full =
|
|
42904
|
-
if (
|
|
43447
|
+
const full = join27(extensionDir, entry2);
|
|
43448
|
+
if (statSync10(full).isDirectory() || entry2.endsWith(".json"))
|
|
42905
43449
|
candidates.push(full);
|
|
42906
43450
|
}
|
|
42907
43451
|
}
|
|
42908
|
-
return candidates.filter(
|
|
43452
|
+
return candidates.filter(existsSync28);
|
|
42909
43453
|
}
|
|
42910
43454
|
function discoverLocalExtensions(options = {}) {
|
|
42911
43455
|
const config = loadConfig();
|
|
42912
|
-
const projectPath = options.project_path ?
|
|
43456
|
+
const projectPath = options.project_path ? resolve19(options.project_path) : null;
|
|
42913
43457
|
const configuredSources = [
|
|
42914
43458
|
...config.extension_sources || [],
|
|
42915
43459
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -42917,7 +43461,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
42917
43461
|
const sources = Array.from(new Set([
|
|
42918
43462
|
...configuredSources,
|
|
42919
43463
|
...projectExtensionSources(projectPath || undefined)
|
|
42920
|
-
])).map((source9) => projectPath && !source9.startsWith("/") ?
|
|
43464
|
+
])).map((source9) => projectPath && !source9.startsWith("/") ? resolve19(projectPath, source9) : resolve19(source9));
|
|
42921
43465
|
const warnings = [];
|
|
42922
43466
|
const discovered = [];
|
|
42923
43467
|
for (const source9 of sources) {
|
|
@@ -42999,7 +43543,7 @@ function removeLocalExtension(name) {
|
|
|
42999
43543
|
return true;
|
|
43000
43544
|
}
|
|
43001
43545
|
function renderExtensionSummary(record) {
|
|
43002
|
-
return `${record.name}@${record.version} ${record.status} ${
|
|
43546
|
+
return `${record.name}@${record.version} ${record.status} ${basename6(record.source)} ${record.signature_verified ? "signed" : "unsigned"}`;
|
|
43003
43547
|
}
|
|
43004
43548
|
// src/lib/workflow-prompts.ts
|
|
43005
43549
|
var COMMON_ARGUMENTS = [
|
|
@@ -43766,7 +44310,7 @@ function resolveMissingTaskFindings(input, db) {
|
|
|
43766
44310
|
init_redaction();
|
|
43767
44311
|
|
|
43768
44312
|
// src/lib/retention-cleanup.ts
|
|
43769
|
-
import { existsSync as
|
|
44313
|
+
import { existsSync as existsSync29, unlinkSync as unlinkSync2 } from "fs";
|
|
43770
44314
|
init_database();
|
|
43771
44315
|
var RETENTION_CLEANUP_CONFIRMATION = "delete-local-retention-data";
|
|
43772
44316
|
var ALL_SCOPES = ["comments", "runs", "verifications", "expired_artifacts"];
|
|
@@ -43978,7 +44522,7 @@ function applyRetentionCleanup(input, db) {
|
|
|
43978
44522
|
for (const artifact of report.candidates.artifact_files) {
|
|
43979
44523
|
try {
|
|
43980
44524
|
const path = artifactStorePath(artifact.relative_path);
|
|
43981
|
-
if (!
|
|
44525
|
+
if (!existsSync29(path)) {
|
|
43982
44526
|
report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
|
|
43983
44527
|
continue;
|
|
43984
44528
|
}
|
|
@@ -44216,8 +44760,8 @@ function renderScalePerformanceReportMarkdown(report) {
|
|
|
44216
44760
|
init_database();
|
|
44217
44761
|
init_migrations();
|
|
44218
44762
|
init_schema();
|
|
44219
|
-
import { chmodSync, copyFileSync as copyFileSync2, existsSync as
|
|
44220
|
-
import { basename as
|
|
44763
|
+
import { chmodSync, copyFileSync as copyFileSync2, existsSync as existsSync30, mkdirSync as mkdirSync22, statSync as statSync11 } from "fs";
|
|
44764
|
+
import { basename as basename7, dirname as dirname19, join as join28 } from "path";
|
|
44221
44765
|
var REQUIRED_TABLES2 = [
|
|
44222
44766
|
"_migrations",
|
|
44223
44767
|
"projects",
|
|
@@ -44324,7 +44868,7 @@ function findMissingProjectRoots(db) {
|
|
|
44324
44868
|
continue;
|
|
44325
44869
|
if (!row.path.startsWith("/"))
|
|
44326
44870
|
continue;
|
|
44327
|
-
if (!
|
|
44871
|
+
if (!existsSync30(row.path))
|
|
44328
44872
|
missing++;
|
|
44329
44873
|
}
|
|
44330
44874
|
return missing;
|
|
@@ -44376,7 +44920,7 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
44376
44920
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
44377
44921
|
return false;
|
|
44378
44922
|
try {
|
|
44379
|
-
return (
|
|
44923
|
+
return (statSync11(dbPath).mode & 63) !== 0;
|
|
44380
44924
|
} catch {
|
|
44381
44925
|
return false;
|
|
44382
44926
|
}
|
|
@@ -44384,16 +44928,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
44384
44928
|
function createBackup(dbPath) {
|
|
44385
44929
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
44386
44930
|
return;
|
|
44387
|
-
if (!
|
|
44931
|
+
if (!existsSync30(dbPath))
|
|
44388
44932
|
return;
|
|
44389
44933
|
const stamp = now().replace(/[:.]/g, "-");
|
|
44390
|
-
const backupDir =
|
|
44934
|
+
const backupDir = join28(dirname19(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
|
|
44391
44935
|
const files = [];
|
|
44392
44936
|
mkdirSync22(backupDir, { recursive: true });
|
|
44393
44937
|
for (const source9 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
44394
|
-
if (!
|
|
44938
|
+
if (!existsSync30(source9))
|
|
44395
44939
|
continue;
|
|
44396
|
-
const target =
|
|
44940
|
+
const target = join28(backupDir, basename7(source9));
|
|
44397
44941
|
copyFileSync2(source9, target);
|
|
44398
44942
|
files.push(target);
|
|
44399
44943
|
}
|
|
@@ -45817,6 +46361,7 @@ export {
|
|
|
45817
46361
|
dispatchToMultiple,
|
|
45818
46362
|
dismissReminder,
|
|
45819
46363
|
discoverVerificationProviderCapabilities,
|
|
46364
|
+
discoverTaskRouteSources,
|
|
45820
46365
|
discoverProjectWorkspace,
|
|
45821
46366
|
discoverLocalExtensions,
|
|
45822
46367
|
detectSourceType,
|
|
@@ -46068,6 +46613,7 @@ export {
|
|
|
46068
46613
|
TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
|
|
46069
46614
|
TASK_STATUSES,
|
|
46070
46615
|
TASK_SCHEDULING_SCHEMA,
|
|
46616
|
+
TASK_ROUTE_SOURCE_DISCOVERY_SCHEMA_VERSION,
|
|
46071
46617
|
TASK_PRIORITIES,
|
|
46072
46618
|
TASK_FINDING_UPSERT_SCHEMA_VERSION,
|
|
46073
46619
|
TASK_FINDING_SCHEMA_VERSION,
|