@hasna/todos 0.11.69 → 0.11.71

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/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, project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
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)
@@ -10736,15 +10782,59 @@ init_database();
10736
10782
  // src/db/plans.ts
10737
10783
  init_types();
10738
10784
  init_database();
10785
+ function planSlugBase2(value) {
10786
+ return slugify(value) || "plan";
10787
+ }
10788
+ function normalizePlanSlug(value) {
10789
+ const slug = slugify(value);
10790
+ if (!slug)
10791
+ throw new Error("Invalid plan slug");
10792
+ return slug;
10793
+ }
10794
+ function plansBySlug(slug, db, projectId) {
10795
+ if (projectId !== undefined) {
10796
+ if (projectId === null) {
10797
+ return db.query("SELECT * FROM plans WHERE slug = ? AND project_id IS NULL ORDER BY created_at ASC, id ASC").all(slug);
10798
+ }
10799
+ return db.query("SELECT * FROM plans WHERE slug = ? AND project_id = ? ORDER BY created_at ASC, id ASC").all(slug, projectId);
10800
+ }
10801
+ return db.query("SELECT * FROM plans WHERE slug = ? ORDER BY created_at ASC, id ASC").all(slug);
10802
+ }
10803
+ function planSlugExists(slug, projectId, db, excludeId) {
10804
+ const rows = plansBySlug(slug, db, projectId);
10805
+ return rows.some((plan) => plan.id !== excludeId);
10806
+ }
10807
+ function nextPlanSlug(base, projectId, db, excludeId) {
10808
+ let candidate = base;
10809
+ let suffix = 2;
10810
+ while (planSlugExists(candidate, projectId, db, excludeId)) {
10811
+ candidate = `${base}-${suffix}`;
10812
+ suffix += 1;
10813
+ }
10814
+ return candidate;
10815
+ }
10816
+ function resolveCreateSlug(input, projectId, db) {
10817
+ if (input.slug !== undefined) {
10818
+ const slug = normalizePlanSlug(input.slug);
10819
+ if (planSlugExists(slug, projectId, db)) {
10820
+ throw new Error(`Plan slug already exists in this scope: ${slug}`);
10821
+ }
10822
+ return slug;
10823
+ }
10824
+ return nextPlanSlug(planSlugBase2(input.name), projectId, db);
10825
+ }
10739
10826
  function createPlan(input, db) {
10740
10827
  const d = db || getDatabase();
10741
10828
  const id = uuid();
10742
10829
  const timestamp = now();
10830
+ const projectId = input.project_id || null;
10831
+ const slug = resolveCreateSlug(input, projectId, d);
10743
10832
  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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10833
+ d.run(`INSERT INTO plans (id, slug, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
10834
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
10746
10835
  id,
10747
- input.project_id || null,
10836
+ slug,
10837
+ projectId,
10748
10838
  input.task_list_id || null,
10749
10839
  input.agent_id || null,
10750
10840
  input.name,
@@ -10779,6 +10869,14 @@ function updatePlan(id, input, db) {
10779
10869
  sets.push("name = ?");
10780
10870
  params.push(input.name);
10781
10871
  }
10872
+ if (input.slug !== undefined) {
10873
+ const slug = normalizePlanSlug(input.slug);
10874
+ if (planSlugExists(slug, plan.project_id, d, id)) {
10875
+ throw new Error(`Plan slug already exists in this scope: ${slug}`);
10876
+ }
10877
+ sets.push("slug = ?");
10878
+ params.push(slug);
10879
+ }
10782
10880
  if (input.description !== undefined) {
10783
10881
  sets.push("description = ?");
10784
10882
  params.push(input.description);
@@ -12632,7 +12730,7 @@ var dataKeys = [
12632
12730
  var insertColumns = {
12633
12731
  projects: ["id", "name", "path", "description", "task_list_id", "task_prefix", "task_counter", "created_at", "updated_at", "machine_id", "synced_at"],
12634
12732
  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"],
12733
+ plans: ["id", "slug", "project_id", "task_list_id", "agent_id", "name", "description", "status", "created_at", "updated_at", "machine_id", "synced_at"],
12636
12734
  tasks: [
12637
12735
  "id",
12638
12736
  "short_id",
@@ -12924,6 +13022,36 @@ function prepareValue(column, value) {
12924
13022
  return JSON.stringify(value ?? (column === "tags" || column === "files_changed" ? [] : {}));
12925
13023
  return value === undefined ? null : value;
12926
13024
  }
13025
+ function slugifyPlanValue(value) {
13026
+ return typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") : "";
13027
+ }
13028
+ function planSlugBase3(plan) {
13029
+ return slugifyPlanValue(plan.slug) || slugifyPlanValue(plan.name) || "plan";
13030
+ }
13031
+ function planSlugScope(projectId) {
13032
+ return typeof projectId === "string" && projectId ? projectId : "__global__";
13033
+ }
13034
+ function planSlugKey(projectId, slug) {
13035
+ return `${planSlugScope(projectId)}:${slug}`;
13036
+ }
13037
+ function normalizeBridgePlanSlugs(plans, db) {
13038
+ const existingRows = db.query("SELECT id, project_id, slug FROM plans WHERE slug IS NOT NULL").all();
13039
+ const existingIds = new Set(existingRows.map((row) => row.id));
13040
+ const used = new Set(existingRows.filter((row) => row.slug).map((row) => planSlugKey(row.project_id, row.slug)));
13041
+ return plans.map((plan) => {
13042
+ if (existingIds.has(plan.id))
13043
+ return plan;
13044
+ const base = planSlugBase3(plan);
13045
+ let candidate = base;
13046
+ let suffix = 2;
13047
+ while (used.has(planSlugKey(plan.project_id, candidate))) {
13048
+ candidate = `${base}-${suffix}`;
13049
+ suffix += 1;
13050
+ }
13051
+ used.add(planSlugKey(plan.project_id, candidate));
13052
+ return { ...plan, slug: candidate };
13053
+ });
13054
+ }
12927
13055
  function insertRecord(db, tableKey, row) {
12928
13056
  const table = tableByKey[tableKey];
12929
13057
  const columns = insertColumns[tableKey];
@@ -13060,6 +13188,7 @@ function importLocalBridgeBundle(bundle, options = {}, db) {
13060
13188
  const conflictStrategy = options.conflictStrategy ?? "skip";
13061
13189
  const data = {
13062
13190
  ...bundle.data,
13191
+ plans: normalizeBridgePlanSlugs(bundle.data.plans, d),
13063
13192
  tasks: sortedTasks(bundle.data.tasks),
13064
13193
  saved_views: bundle.data.saved_views ?? [],
13065
13194
  task_boards: bundle.data.task_boards ?? [],
@@ -13253,6 +13382,7 @@ function createAgentProjectDemoBundle() {
13253
13382
  });
13254
13383
  data.plans.push({
13255
13384
  id: ids.plan,
13385
+ slug: "ship-local-demo-workflow",
13256
13386
  project_id: ids.project,
13257
13387
  task_list_id: ids.list,
13258
13388
  agent_id: "demo-agent",
@@ -23380,9 +23510,17 @@ async function updateProject2(id, input, store) {
23380
23510
  }
23381
23511
  async function createPlan2(input, store, context) {
23382
23512
  const timestamp2 = new Date().toISOString();
23513
+ const projectId = input.project_id ?? context?.projectId ?? null;
23514
+ const slug = await resolvePostgresPlanSlug({
23515
+ name: input.name,
23516
+ slug: input.slug,
23517
+ projectId,
23518
+ store
23519
+ });
23383
23520
  return store.upsert("plans", {
23384
23521
  id: randomUUID3(),
23385
- project_id: input.project_id ?? context?.projectId ?? null,
23522
+ slug,
23523
+ project_id: projectId,
23386
23524
  task_list_id: input.task_list_id ?? context?.taskListId ?? null,
23387
23525
  agent_id: input.agent_id ?? context?.agentId ?? null,
23388
23526
  name: input.name,
@@ -23396,7 +23534,17 @@ async function createPlan2(input, store, context) {
23396
23534
  }
23397
23535
  async function updatePlan2(id, input, store) {
23398
23536
  const plan = await requireRecord("plans", id, store);
23399
- return store.upsert("plans", { ...plan, ...definedPatch(input), updated_at: new Date().toISOString() });
23537
+ const patch = definedPatch(input);
23538
+ if (input.slug !== undefined) {
23539
+ patch.slug = await resolvePostgresPlanSlug({
23540
+ name: plan.name,
23541
+ slug: input.slug,
23542
+ projectId: plan.project_id,
23543
+ store,
23544
+ excludeId: id
23545
+ });
23546
+ }
23547
+ return store.upsert("plans", { ...plan, ...patch, updated_at: new Date().toISOString() });
23400
23548
  }
23401
23549
  async function registerAgent2(input, store, context) {
23402
23550
  const existing = (await store.list("agents")).find((agent2) => agent2.name === input.name && agent2.status !== "archived");
@@ -23649,8 +23797,38 @@ function matchesOne(value, expected) {
23649
23797
  function priorityRank(priority) {
23650
23798
  return { critical: 0, high: 1, medium: 2, low: 3 }[priority];
23651
23799
  }
23800
+ function slugifyRaw(value) {
23801
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
23802
+ }
23652
23803
  function slugify2(value) {
23653
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "todos";
23804
+ return slugifyRaw(value) || "todos";
23805
+ }
23806
+ function normalizePlanSlug2(value) {
23807
+ const slug = slugifyRaw(value);
23808
+ if (!slug)
23809
+ throw new Error("Invalid plan slug");
23810
+ return slug;
23811
+ }
23812
+ function planSlugBase4(value) {
23813
+ return slugifyRaw(value) || "plan";
23814
+ }
23815
+ async function resolvePostgresPlanSlug(options) {
23816
+ const plans = await options.store.list("plans");
23817
+ const used = new Set(plans.filter((plan) => plan.project_id === options.projectId && plan.id !== options.excludeId && plan.slug).map((plan) => plan.slug));
23818
+ if (options.slug !== undefined) {
23819
+ const slug = normalizePlanSlug2(options.slug);
23820
+ if (used.has(slug))
23821
+ throw new Error(`Plan slug already exists in this scope: ${slug}`);
23822
+ return slug;
23823
+ }
23824
+ const base = planSlugBase4(options.name);
23825
+ let candidate = base;
23826
+ let suffix = 2;
23827
+ while (used.has(candidate)) {
23828
+ candidate = `${base}-${suffix}`;
23829
+ suffix += 1;
23830
+ }
23831
+ return candidate;
23654
23832
  }
23655
23833
  function definedPatch(value) {
23656
23834
  return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined));
@@ -24961,6 +25139,318 @@ function listCyclesWithStats(options = {}, db) {
24961
25139
  };
24962
25140
  });
24963
25141
  }
25142
+ // src/lib/plan-artifacts.ts
25143
+ init_database();
25144
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
25145
+ import { join as join10, resolve as resolve10 } from "path";
25146
+ var PLAN_MARKDOWN_SCHEMA = "hasna.todos.plan/v1";
25147
+ function assertSafePathSegment(value, label) {
25148
+ const trimmed = value.trim();
25149
+ if (!trimmed || trimmed === "." || trimmed === ".." || trimmed.includes("/") || trimmed.includes("\\")) {
25150
+ throw new Error(`Invalid ${label} for plan artifact path`);
25151
+ }
25152
+ if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) {
25153
+ throw new Error(`Invalid ${label} for plan artifact path`);
25154
+ }
25155
+ return trimmed;
25156
+ }
25157
+ function frontmatterScalar(value) {
25158
+ return JSON.stringify(value);
25159
+ }
25160
+ function parseFrontmatterScalar(value) {
25161
+ const trimmed = value.trim();
25162
+ if (trimmed === "null")
25163
+ return null;
25164
+ try {
25165
+ const parsed = JSON.parse(trimmed);
25166
+ if (parsed === null || typeof parsed === "string")
25167
+ return parsed;
25168
+ } catch {}
25169
+ return trimmed.replace(/^["']|["']$/g, "") || null;
25170
+ }
25171
+ function markdownEscape(text) {
25172
+ return text.replace(/<!--[\s\S]*?-->/g, "").trim();
25173
+ }
25174
+ function markdownLine(text) {
25175
+ return markdownEscape(text).replace(/\s+/g, " ").trim();
25176
+ }
25177
+ function projectSlugMatches(project, ref) {
25178
+ const normalized = slugify(ref);
25179
+ return Boolean(normalized) && (project.task_list_id === normalized || slugify(project.name) === normalized);
25180
+ }
25181
+ function planArtifactSlug(plan) {
25182
+ return slugify(plan.slug || plan.name) || "plan";
25183
+ }
25184
+ function resolvePlanArtifactProject(input) {
25185
+ const db = input.db || getDatabase();
25186
+ const ref = input.project_id || input.project_ref;
25187
+ if (!ref)
25188
+ throw new Error("Plan artifacts require a project id or project reference");
25189
+ const byPath = getProjectByPath(resolve10(ref), db);
25190
+ if (byPath)
25191
+ return byPath;
25192
+ const resolvedId = resolvePartialId(db, "projects", ref);
25193
+ if (resolvedId) {
25194
+ const project2 = getProject(resolvedId, db);
25195
+ if (project2)
25196
+ return project2;
25197
+ }
25198
+ const project = listProjects(db).find((candidate) => projectSlugMatches(candidate, ref));
25199
+ if (project)
25200
+ return project;
25201
+ throw new Error(`Project not found for plan artifacts: ${ref}`);
25202
+ }
25203
+ function resolvePlanArtifactPaths(input) {
25204
+ const project = resolvePlanArtifactProject(input);
25205
+ const projectId = assertSafePathSegment(project.id, "project id");
25206
+ const projectRoot = resolve10(project.path);
25207
+ const directory = join10(projectRoot, ".hasna", "todos", "plans", projectId);
25208
+ const planId = input.plan_id ? assertSafePathSegment(input.plan_id, "plan id") : null;
25209
+ const planSlug = input.plan_slug ? assertSafePathSegment(slugify(input.plan_slug), "plan slug") : null;
25210
+ const fileName = planId ? planSlug ? `${planSlug}--${planId.slice(0, 8)}.md` : `${planId}.md` : null;
25211
+ return {
25212
+ project_id: project.id,
25213
+ project_root: projectRoot,
25214
+ directory,
25215
+ file_path: fileName ? join10(directory, fileName) : directory
25216
+ };
25217
+ }
25218
+ function resolvePlanArtifactCandidatePaths(plan, db) {
25219
+ return {
25220
+ primary: resolvePlanArtifactPaths({
25221
+ project_id: plan.project_id,
25222
+ plan_id: plan.id,
25223
+ plan_slug: planArtifactSlug(plan),
25224
+ db
25225
+ }),
25226
+ legacy: resolvePlanArtifactPaths({ project_id: plan.project_id, plan_id: plan.id, db })
25227
+ };
25228
+ }
25229
+ function buildPlanArtifactSnapshot(plan, tasks = [], artifactUpdatedAt = new Date().toISOString()) {
25230
+ if (!plan.project_id)
25231
+ throw new Error("Plan artifacts require a project-scoped plan");
25232
+ const taskReferences = tasks.map((task2) => ({
25233
+ task_id: task2.id,
25234
+ title: task2.title,
25235
+ status: task2.status,
25236
+ priority: task2.priority
25237
+ }));
25238
+ const body = renderPlanArtifactBody(plan, taskReferences);
25239
+ return {
25240
+ metadata: {
25241
+ schema: PLAN_MARKDOWN_SCHEMA,
25242
+ plan_id: plan.id,
25243
+ plan_slug: plan.slug ?? null,
25244
+ project_id: plan.project_id,
25245
+ task_list_id: plan.task_list_id ?? null,
25246
+ agent_id: plan.agent_id ?? null,
25247
+ stable_id: plan.id,
25248
+ name: plan.name,
25249
+ status: plan.status,
25250
+ created_at: plan.created_at,
25251
+ updated_at: plan.updated_at,
25252
+ artifact_updated_at: artifactUpdatedAt
25253
+ },
25254
+ task_references: taskReferences,
25255
+ body
25256
+ };
25257
+ }
25258
+ function renderPlanArtifactBody(plan, tasks) {
25259
+ const lines = [`# ${markdownLine(plan.name) || plan.id}`, ""];
25260
+ if (plan.description?.trim()) {
25261
+ lines.push(markdownEscape(plan.description), "");
25262
+ }
25263
+ lines.push("## Tasks", "");
25264
+ if (tasks.length === 0) {
25265
+ lines.push("_No tasks are currently attached to this plan._", "");
25266
+ } else {
25267
+ for (const task2 of tasks) {
25268
+ const check = task2.status === "completed" ? "x" : " ";
25269
+ lines.push(`- [${check}] ${markdownLine(task2.title) || task2.task_id}`);
25270
+ lines.push(` <!-- todos: task_id=${task2.task_id} status=${task2.status} priority=${task2.priority} -->`);
25271
+ }
25272
+ lines.push("");
25273
+ }
25274
+ return lines.join(`
25275
+ `);
25276
+ }
25277
+ function renderPlanArtifactMarkdown(snapshot) {
25278
+ const metadata = snapshot.metadata;
25279
+ const lines = [
25280
+ "---",
25281
+ `schema: ${frontmatterScalar(metadata.schema)}`,
25282
+ `plan_id: ${frontmatterScalar(metadata.plan_id)}`,
25283
+ `plan_slug: ${frontmatterScalar(metadata.plan_slug)}`,
25284
+ `project_id: ${frontmatterScalar(metadata.project_id)}`,
25285
+ `task_list_id: ${frontmatterScalar(metadata.task_list_id)}`,
25286
+ `agent_id: ${frontmatterScalar(metadata.agent_id)}`,
25287
+ `stable_id: ${frontmatterScalar(metadata.stable_id)}`,
25288
+ `name: ${frontmatterScalar(metadata.name)}`,
25289
+ `status: ${frontmatterScalar(metadata.status)}`,
25290
+ `created_at: ${frontmatterScalar(metadata.created_at)}`,
25291
+ `updated_at: ${frontmatterScalar(metadata.updated_at)}`,
25292
+ `artifact_updated_at: ${frontmatterScalar(metadata.artifact_updated_at)}`,
25293
+ "---",
25294
+ "",
25295
+ snapshot.body
25296
+ ];
25297
+ return `${lines.join(`
25298
+ `).replace(/\n{3,}/g, `
25299
+
25300
+ `).trimEnd()}
25301
+ `;
25302
+ }
25303
+ function parsePlanArtifactMarkdown(markdown) {
25304
+ const match = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
25305
+ if (!match)
25306
+ throw new Error("Invalid plan artifact: missing frontmatter");
25307
+ const rawMetadata = {};
25308
+ for (const line of match[1].split(/\r?\n/)) {
25309
+ const separator = line.indexOf(":");
25310
+ if (separator === -1)
25311
+ continue;
25312
+ const key = line.slice(0, separator).trim();
25313
+ const value = line.slice(separator + 1);
25314
+ rawMetadata[key] = parseFrontmatterScalar(value);
25315
+ }
25316
+ if (rawMetadata.schema !== PLAN_MARKDOWN_SCHEMA) {
25317
+ throw new Error(`Unsupported plan artifact schema: ${rawMetadata.schema ?? "unknown"}`);
25318
+ }
25319
+ const required = ["plan_id", "project_id", "stable_id", "name", "status", "created_at", "updated_at", "artifact_updated_at"];
25320
+ for (const key of required) {
25321
+ if (!rawMetadata[key])
25322
+ throw new Error(`Invalid plan artifact: missing ${key}`);
25323
+ }
25324
+ const body = match[2] ?? "";
25325
+ return {
25326
+ metadata: {
25327
+ schema: PLAN_MARKDOWN_SCHEMA,
25328
+ plan_id: rawMetadata.plan_id,
25329
+ plan_slug: rawMetadata.plan_slug ?? null,
25330
+ project_id: rawMetadata.project_id,
25331
+ task_list_id: rawMetadata.task_list_id ?? null,
25332
+ agent_id: rawMetadata.agent_id ?? null,
25333
+ stable_id: rawMetadata.stable_id,
25334
+ name: rawMetadata.name,
25335
+ status: rawMetadata.status,
25336
+ created_at: rawMetadata.created_at,
25337
+ updated_at: rawMetadata.updated_at,
25338
+ artifact_updated_at: rawMetadata.artifact_updated_at
25339
+ },
25340
+ task_references: parseTaskReferences(body),
25341
+ body
25342
+ };
25343
+ }
25344
+ function parseTaskReferences(body) {
25345
+ const references = [];
25346
+ const taskLine2 = /^\s*-\s+\[[ xX]\]\s+(.+)$/;
25347
+ const metadataLine = /<!--\s*todos:\s*task_id=([A-Za-z0-9._-]+)\s+status=([A-Za-z_]+)\s+priority=([A-Za-z_]+)\s*-->/;
25348
+ const lines = body.split(/\r?\n/);
25349
+ for (let index = 0;index < lines.length; index++) {
25350
+ const titleMatch = lines[index].match(taskLine2);
25351
+ if (!titleMatch)
25352
+ continue;
25353
+ const metadataMatch = lines[index + 1]?.match(metadataLine);
25354
+ if (!metadataMatch)
25355
+ continue;
25356
+ references.push({
25357
+ task_id: metadataMatch[1],
25358
+ title: titleMatch[1].trim(),
25359
+ status: metadataMatch[2],
25360
+ priority: metadataMatch[3]
25361
+ });
25362
+ }
25363
+ return references;
25364
+ }
25365
+ function writePlanArtifact(plan, db) {
25366
+ if (!plan.project_id)
25367
+ return null;
25368
+ const d = db || getDatabase();
25369
+ const tasks = listTasks({ plan_id: plan.id, include_archived: true }, d);
25370
+ const paths = resolvePlanArtifactCandidatePaths(plan, d).primary;
25371
+ const snapshot = buildPlanArtifactSnapshot(plan, tasks);
25372
+ mkdirSync8(paths.directory, { recursive: true });
25373
+ writeFileSync6(paths.file_path, renderPlanArtifactMarkdown(snapshot), "utf8");
25374
+ return { path: paths.file_path, snapshot };
25375
+ }
25376
+ function readPlanArtifact(plan, db) {
25377
+ if (!plan.project_id)
25378
+ return null;
25379
+ const d = db || getDatabase();
25380
+ const paths = resolvePlanArtifactCandidatePaths(plan, d);
25381
+ const path = existsSync9(paths.primary.file_path) ? paths.primary.file_path : existsSync9(paths.legacy.file_path) ? paths.legacy.file_path : null;
25382
+ if (!path)
25383
+ return null;
25384
+ const markdown = readFileSync7(path, "utf8");
25385
+ return {
25386
+ path,
25387
+ markdown,
25388
+ ...parsePlanArtifactMarkdown(markdown)
25389
+ };
25390
+ }
25391
+ function inspectPlanArtifact(plan, db) {
25392
+ if (!plan.project_id)
25393
+ return null;
25394
+ const d = db || getDatabase();
25395
+ const paths = resolvePlanArtifactCandidatePaths(plan, d);
25396
+ const path = existsSync9(paths.primary.file_path) ? paths.primary.file_path : existsSync9(paths.legacy.file_path) ? paths.legacy.file_path : null;
25397
+ if (!path) {
25398
+ return {
25399
+ path: paths.primary.file_path,
25400
+ exists: false,
25401
+ parse_error: null,
25402
+ metadata: null,
25403
+ task_references: [],
25404
+ conflicts: []
25405
+ };
25406
+ }
25407
+ try {
25408
+ const artifact = parsePlanArtifactMarkdown(readFileSync7(path, "utf8"));
25409
+ return {
25410
+ path,
25411
+ exists: true,
25412
+ parse_error: null,
25413
+ metadata: artifact.metadata,
25414
+ task_references: artifact.task_references,
25415
+ conflicts: comparePlanArtifact(plan, artifact, listTasks({ plan_id: plan.id, include_archived: true }, d))
25416
+ };
25417
+ } catch (error) {
25418
+ return {
25419
+ path,
25420
+ exists: true,
25421
+ parse_error: error instanceof Error ? error.message : String(error),
25422
+ metadata: null,
25423
+ task_references: [],
25424
+ conflicts: []
25425
+ };
25426
+ }
25427
+ }
25428
+ function comparePlanArtifact(plan, artifact, tasks) {
25429
+ const conflicts = [];
25430
+ compare("plan_id", plan.id, artifact.metadata.plan_id, conflicts);
25431
+ if (artifact.metadata.plan_slug !== null) {
25432
+ compare("plan_slug", plan.slug ?? null, artifact.metadata.plan_slug, conflicts);
25433
+ }
25434
+ compare("project_id", plan.project_id ?? null, artifact.metadata.project_id, conflicts);
25435
+ compare("name", plan.name, artifact.metadata.name, conflicts);
25436
+ compare("status", plan.status, artifact.metadata.status, conflicts);
25437
+ compare("updated_at", plan.updated_at, artifact.metadata.updated_at, conflicts);
25438
+ const dbTaskIds = tasks.map((task2) => task2.id).sort();
25439
+ const artifactTaskIds = artifact.task_references.map((task2) => task2.task_id).sort();
25440
+ if (dbTaskIds.join(",") !== artifactTaskIds.join(",")) {
25441
+ conflicts.push({
25442
+ field: "task_references",
25443
+ database: dbTaskIds.join(",") || null,
25444
+ artifact: artifactTaskIds.join(",") || null
25445
+ });
25446
+ }
25447
+ return conflicts;
25448
+ }
25449
+ function compare(field2, database, artifact, conflicts) {
25450
+ if ((database ?? null) !== (artifact ?? null)) {
25451
+ conflicts.push({ field: field2, database: database ?? null, artifact: artifact ?? null });
25452
+ }
25453
+ }
24964
25454
  // src/db/project-knowledge.ts
24965
25455
  init_database();
24966
25456
 
@@ -25989,8 +26479,8 @@ function renderRetrospectiveMarkdown(record) {
25989
26479
  }
25990
26480
  // src/lib/project-bootstrap.ts
25991
26481
  init_database();
25992
- import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
25993
- import { basename as basename2, dirname as dirname7, resolve as resolve10 } from "path";
26482
+ import { existsSync as existsSync10, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
26483
+ import { basename as basename2, dirname as dirname7, resolve as resolve11 } from "path";
25994
26484
  function safeStat(path) {
25995
26485
  try {
25996
26486
  return statSync3(path);
@@ -25999,7 +26489,7 @@ function safeStat(path) {
25999
26489
  }
26000
26490
  }
26001
26491
  function canonicalPath(input) {
26002
- const resolved = resolve10(input);
26492
+ const resolved = resolve11(input);
26003
26493
  const stats2 = safeStat(resolved);
26004
26494
  if (stats2?.isFile())
26005
26495
  return dirname7(resolved);
@@ -26008,7 +26498,7 @@ function canonicalPath(input) {
26008
26498
  function findUp(start, marker) {
26009
26499
  let current = canonicalPath(start);
26010
26500
  while (true) {
26011
- if (existsSync9(resolve10(current, marker)))
26501
+ if (existsSync10(resolve11(current, marker)))
26012
26502
  return current;
26013
26503
  const parent = dirname7(current);
26014
26504
  if (parent === current)
@@ -26019,11 +26509,11 @@ function findUp(start, marker) {
26019
26509
  function readPackageJson2(path) {
26020
26510
  if (!path)
26021
26511
  return null;
26022
- const file = resolve10(path, "package.json");
26023
- if (!existsSync9(file))
26512
+ const file = resolve11(path, "package.json");
26513
+ if (!existsSync10(file))
26024
26514
  return null;
26025
26515
  try {
26026
- const parsed = JSON.parse(readFileSync7(file, "utf-8"));
26516
+ const parsed = JSON.parse(readFileSync8(file, "utf-8"));
26027
26517
  return parsed && typeof parsed === "object" ? parsed : null;
26028
26518
  } catch {
26029
26519
  return null;
@@ -26042,7 +26532,7 @@ function workspaceMarker(root, rootPackage) {
26042
26532
  if (rootPackage?.workspaces)
26043
26533
  markers.push("package.json#workspaces");
26044
26534
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
26045
- if (existsSync9(resolve10(root, marker)))
26535
+ if (existsSync10(resolve11(root, marker)))
26046
26536
  markers.push(marker);
26047
26537
  }
26048
26538
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -26535,21 +27025,21 @@ var gatherTrainingData = async (options = {}) => {
26535
27025
  };
26536
27026
  // src/lib/model-config.ts
26537
27027
  init_sync_utils();
26538
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
26539
- import { join as join10 } from "path";
27028
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
27029
+ import { join as join11 } from "path";
26540
27030
  var DEFAULT_MODEL = "gpt-4o-mini";
26541
27031
  function getConfigDir() {
26542
27032
  return getTodosGlobalDir();
26543
27033
  }
26544
27034
  function getConfigPath2() {
26545
- return join10(getConfigDir(), "config.json");
27035
+ return join11(getConfigDir(), "config.json");
26546
27036
  }
26547
27037
  function readConfig() {
26548
27038
  const configPath = getConfigPath2();
26549
- if (!existsSync10(configPath))
27039
+ if (!existsSync11(configPath))
26550
27040
  return {};
26551
27041
  try {
26552
- const raw = readFileSync8(configPath, "utf-8");
27042
+ const raw = readFileSync9(configPath, "utf-8");
26553
27043
  return JSON.parse(raw);
26554
27044
  } catch {
26555
27045
  return {};
@@ -26557,10 +27047,10 @@ function readConfig() {
26557
27047
  }
26558
27048
  function writeConfig(config) {
26559
27049
  const configDir = getConfigDir();
26560
- if (!existsSync10(configDir)) {
26561
- mkdirSync8(configDir, { recursive: true });
27050
+ if (!existsSync11(configDir)) {
27051
+ mkdirSync9(configDir, { recursive: true });
26562
27052
  }
26563
- writeFileSync6(getConfigPath2(), JSON.stringify(config, null, 2) + `
27053
+ writeFileSync7(getConfigPath2(), JSON.stringify(config, null, 2) + `
26564
27054
  `, "utf-8");
26565
27055
  }
26566
27056
  function getActiveModel() {
@@ -27282,7 +27772,7 @@ CLI equivalent: \`${r.equivalent_cli}\`
27282
27772
  `);
27283
27773
  }
27284
27774
  // src/lib/verification-providers.ts
27285
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
27775
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
27286
27776
  init_database();
27287
27777
  init_config();
27288
27778
  init_redaction();
@@ -27393,7 +27883,7 @@ function classifyLog(text) {
27393
27883
  async function sleep2(ms) {
27394
27884
  if (ms <= 0)
27395
27885
  return;
27396
- await new Promise((resolve11) => setTimeout(resolve11, ms));
27886
+ await new Promise((resolve12) => setTimeout(resolve12, ms));
27397
27887
  }
27398
27888
  async function runCommandProvider(provider, input) {
27399
27889
  const commandTemplate = input.command || provider.command;
@@ -27448,7 +27938,7 @@ Timed out after ${provider.timeout_ms}ms`);
27448
27938
  };
27449
27939
  }
27450
27940
  function runCiLogProvider(input) {
27451
- const text = input.log_text ?? (input.log_path && existsSync11(input.log_path) ? readFileSync9(input.log_path, "utf-8") : "");
27941
+ const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync10(input.log_path, "utf-8") : "");
27452
27942
  return {
27453
27943
  status: classifyLog(text),
27454
27944
  attempts: 1,
@@ -27460,7 +27950,7 @@ function runBrowserProvider(input) {
27460
27950
  if (!input.artifact_path) {
27461
27951
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
27462
27952
  }
27463
- if (!existsSync11(input.artifact_path)) {
27953
+ if (!existsSync12(input.artifact_path)) {
27464
27954
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
27465
27955
  }
27466
27956
  return {
@@ -27624,7 +28114,7 @@ function listVerificationRecords(filter = {}, db) {
27624
28114
  }
27625
28115
  // src/lib/verification-evidence.ts
27626
28116
  init_database();
27627
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync9 } from "fs";
28117
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10 } from "fs";
27628
28118
  import { dirname as dirname8 } from "path";
27629
28119
  var VERIFICATION_EVIDENCE_SCHEMA = "todos.verification_evidence.v1";
27630
28120
  function getMachineId3() {
@@ -27745,15 +28235,15 @@ function exportVerificationEvidence(filter = {}, db) {
27745
28235
  };
27746
28236
  }
27747
28237
  function writeVerificationExport(bundle, path) {
27748
- mkdirSync9(dirname8(path), { recursive: true });
27749
- writeFileSync7(path, JSON.stringify(bundle, null, 2), "utf8");
28238
+ mkdirSync10(dirname8(path), { recursive: true });
28239
+ writeFileSync8(path, JSON.stringify(bundle, null, 2), "utf8");
27750
28240
  }
27751
28241
  // src/lib/policy-packs.ts
27752
- import { relative as relative3, resolve as resolve11 } from "path";
28242
+ import { relative as relative3, resolve as resolve12 } from "path";
27753
28243
  init_database();
27754
28244
  init_config();
27755
28245
  function normalizePath3(path) {
27756
- return resolve11(path);
28246
+ return resolve12(path);
27757
28247
  }
27758
28248
  function unique4(values) {
27759
28249
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -27808,7 +28298,7 @@ function commandMatches(commands, pattern) {
27808
28298
  }
27809
28299
  function pathMatches(paths, pattern, root) {
27810
28300
  return paths.filter((path) => {
27811
- const candidate = path.startsWith("/") ? path : resolve11(root, path);
28301
+ const candidate = path.startsWith("/") ? path : resolve12(root, path);
27812
28302
  if (!isPathInside3(root, candidate))
27813
28303
  return matchesPattern3(path, pattern);
27814
28304
  return matchesPattern3(path, pattern) || matchesPattern3(relative3(root, candidate), pattern);
@@ -28099,21 +28589,21 @@ function resourceDiagnostics() {
28099
28589
  };
28100
28590
  }
28101
28591
  // src/lib/sandbox-profiles.ts
28102
- import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync10 } from "fs";
28103
- import { join as join11, dirname as dirname9 } from "path";
28592
+ import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync9, mkdirSync as mkdirSync11 } from "fs";
28593
+ import { join as join12, dirname as dirname9 } from "path";
28104
28594
  var SANDBOX_PROFILE_VERSION = "todos.sandbox-profile.v1";
28105
28595
  function getProfilesPath() {
28106
28596
  if (process.env["TODOS_SANDBOX_PROFILES_PATH"]) {
28107
28597
  return process.env["TODOS_SANDBOX_PROFILES_PATH"];
28108
28598
  }
28109
- const localDir = join11(process.cwd(), ".todos");
28110
- const local = join11(localDir, "sandbox-profiles.json");
28111
- if (existsSync12(localDir))
28599
+ const localDir = join12(process.cwd(), ".todos");
28600
+ const local = join12(localDir, "sandbox-profiles.json");
28601
+ if (existsSync13(localDir))
28112
28602
  return local;
28113
- if (existsSync12(local))
28603
+ if (existsSync13(local))
28114
28604
  return local;
28115
28605
  const home = process.env["HOME"] || "~";
28116
- return join11(home, ".hasna", "todos", "sandbox-profiles.json");
28606
+ return join12(home, ".hasna", "todos", "sandbox-profiles.json");
28117
28607
  }
28118
28608
  var cached2 = null;
28119
28609
  function resetSandboxProfileCache() {
@@ -28145,11 +28635,11 @@ function loadSandboxProfiles() {
28145
28635
  if (cached2)
28146
28636
  return cached2;
28147
28637
  const path = getProfilesPath();
28148
- if (!existsSync12(path)) {
28638
+ if (!existsSync13(path)) {
28149
28639
  cached2 = getDefaultSandboxProfiles();
28150
28640
  return cached2;
28151
28641
  }
28152
- const parsed = JSON.parse(readFileSync10(path, "utf8"));
28642
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
28153
28643
  cached2 = parsed.profiles?.length ? parsed.profiles : getDefaultSandboxProfiles();
28154
28644
  return cached2;
28155
28645
  }
@@ -28158,8 +28648,8 @@ function getSandboxProfile(name) {
28158
28648
  }
28159
28649
  function saveSandboxProfiles(profiles) {
28160
28650
  const path = getProfilesPath();
28161
- mkdirSync10(dirname9(path), { recursive: true });
28162
- writeFileSync8(path, JSON.stringify({ schema_version: SANDBOX_PROFILE_VERSION, profiles }, null, 2));
28651
+ mkdirSync11(dirname9(path), { recursive: true });
28652
+ writeFileSync9(path, JSON.stringify({ schema_version: SANDBOX_PROFILE_VERSION, profiles }, null, 2));
28163
28653
  cached2 = profiles;
28164
28654
  }
28165
28655
  function commandMatchesAllowlist(command, allow) {
@@ -28518,9 +29008,9 @@ function getDefaultAgentAdapters() {
28518
29008
  }
28519
29009
  function resetAgentAdapterCache() {}
28520
29010
  // src/lib/git-traceability.ts
28521
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
29011
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
28522
29012
  import { spawnSync as spawnSync2 } from "child_process";
28523
- import { resolve as resolve12 } from "path";
29013
+ import { resolve as resolve13 } from "path";
28524
29014
  var GIT_TRACEABILITY_SCHEMA_VERSION = "todos.git_traceability.v1";
28525
29015
  function runGit(args, cwd) {
28526
29016
  const result = spawnSync2("git", args, { cwd, encoding: "utf8" });
@@ -28563,11 +29053,11 @@ function inspectGitCommit(sha, cwd) {
28563
29053
  };
28564
29054
  }
28565
29055
  function loadCiSnapshot(path) {
28566
- const target = path ? resolve12(path) : resolve12(process.cwd(), ".todos", "ci-snapshot.json");
28567
- if (!existsSync13(target))
29056
+ const target = path ? resolve13(path) : resolve13(process.cwd(), ".todos", "ci-snapshot.json");
29057
+ if (!existsSync14(target))
28568
29058
  return null;
28569
29059
  try {
28570
- const parsed = JSON.parse(readFileSync11(target, "utf8"));
29060
+ const parsed = JSON.parse(readFileSync12(target, "utf8"));
28571
29061
  return { ...parsed, captured_at: parsed.captured_at ?? new Date().toISOString() };
28572
29062
  } catch {
28573
29063
  return null;
@@ -28660,8 +29150,8 @@ function formatTraceabilityReport(report) {
28660
29150
  `);
28661
29151
  }
28662
29152
  // src/lib/mention-resolver.ts
28663
- import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
28664
- import { basename as basename3, isAbsolute, join as join12, relative as relative4, resolve as resolve13, sep as sep2 } from "path";
29153
+ import { existsSync as existsSync15, readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
29154
+ import { basename as basename3, isAbsolute, join as join13, relative as relative4, resolve as resolve14, sep as sep2 } from "path";
28665
29155
  init_database();
28666
29156
  var PREFIXES = {
28667
29157
  file: "file",
@@ -28737,7 +29227,7 @@ function backlink(kind, key, label, target = key) {
28737
29227
  return { kind, key, label, target };
28738
29228
  }
28739
29229
  function normalizeWorkspace(workspace) {
28740
- return resolve13(workspace || process.cwd());
29230
+ return resolve14(workspace || process.cwd());
28741
29231
  }
28742
29232
  function isInside(root, absolutePath) {
28743
29233
  const rel = relative4(root, absolutePath);
@@ -28805,14 +29295,14 @@ function resolveFile(parsed, workspace) {
28805
29295
  resolution.warnings.push("path is empty or escapes the workspace");
28806
29296
  return resolution;
28807
29297
  }
28808
- const absolutePath = resolve13(workspace, relPath);
29298
+ const absolutePath = resolve14(workspace, relPath);
28809
29299
  if (!isInside(workspace, absolutePath)) {
28810
29300
  resolution.path = relPath;
28811
29301
  resolution.warnings.push("path escapes the workspace");
28812
29302
  return resolution;
28813
29303
  }
28814
29304
  resolution.path = relPath;
28815
- if (!existsSync14(absolutePath)) {
29305
+ if (!existsSync15(absolutePath)) {
28816
29306
  resolution.warnings.push("file does not exist in the local workspace");
28817
29307
  return resolution;
28818
29308
  }
@@ -28822,7 +29312,7 @@ function resolveFile(parsed, workspace) {
28822
29312
  return resolution;
28823
29313
  }
28824
29314
  if (parsed.line !== undefined) {
28825
- const lineCount = readFileSync12(absolutePath, "utf-8").split(/\r?\n/).length;
29315
+ const lineCount = readFileSync13(absolutePath, "utf-8").split(/\r?\n/).length;
28826
29316
  if (parsed.line < 1 || parsed.line > lineCount) {
28827
29317
  resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
28828
29318
  return resolution;
@@ -28845,7 +29335,7 @@ function walkSourceFiles(root, current = root, files = []) {
28845
29335
  if (SKIP_DIRS.has(entry2.name))
28846
29336
  continue;
28847
29337
  }
28848
- const absolutePath = join12(current, entry2.name);
29338
+ const absolutePath = join13(current, entry2.name);
28849
29339
  if (entry2.isDirectory()) {
28850
29340
  if (!SKIP_DIRS.has(entry2.name))
28851
29341
  walkSourceFiles(root, absolutePath, files);
@@ -28875,7 +29365,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
28875
29365
  const pattern = symbolPattern(name);
28876
29366
  const matches = [];
28877
29367
  for (const file of walkSourceFiles(workspace)) {
28878
- const lines = readFileSync12(file, "utf-8").split(/\r?\n/);
29368
+ const lines = readFileSync13(file, "utf-8").split(/\r?\n/);
28879
29369
  for (let index = 0;index < lines.length; index += 1) {
28880
29370
  const line = lines[index];
28881
29371
  const found = pattern.exec(line);
@@ -30725,7 +31215,7 @@ function getAdapterDocsFingerprint() {
30725
31215
  }
30726
31216
  // src/lib/inbox-intake.ts
30727
31217
  init_database();
30728
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
31218
+ import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
30729
31219
  import { basename as basename4 } from "path";
30730
31220
  import { createHash as createHash11 } from "crypto";
30731
31221
  init_secret_redaction();
@@ -30771,9 +31261,9 @@ function loadRawContent(input) {
30771
31261
  }
30772
31262
  }
30773
31263
  if (input.file_path) {
30774
- if (!existsSync15(input.file_path))
31264
+ if (!existsSync16(input.file_path))
30775
31265
  throw new Error(`File not found: ${input.file_path}`);
30776
- const raw = readFileSync13(input.file_path, "utf8");
31266
+ const raw = readFileSync14(input.file_path, "utf8");
30777
31267
  const name = basename4(input.file_path).toLowerCase();
30778
31268
  const source_type2 = input.source_type ?? (name.includes("ci") || name.endsWith(".log") ? "ci_log" : "file");
30779
31269
  return {
@@ -31389,7 +31879,7 @@ function formatNlIntakePreviewText(preview) {
31389
31879
  }
31390
31880
  // src/lib/issue-importers.ts
31391
31881
  init_database();
31392
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
31882
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
31393
31883
  var ISSUE_IMPORT_SCHEMA = "todos.issue_import.v1";
31394
31884
  var ISSUE_SOURCES = ["github", "linear", "jira", "auto"];
31395
31885
  var GITHUB_LABEL_PRIORITY = {
@@ -31608,9 +32098,9 @@ function parseIssueExport(data, source9 = "auto") {
31608
32098
  return normalized;
31609
32099
  }
31610
32100
  function loadIssueExportFromFile(path) {
31611
- if (!existsSync16(path))
32101
+ if (!existsSync17(path))
31612
32102
  throw new Error(`File not found: ${path}`);
31613
- return JSON.parse(readFileSync14(path, "utf8"));
32103
+ return JSON.parse(readFileSync15(path, "utf8"));
31614
32104
  }
31615
32105
  function loadIssueExportInput(input) {
31616
32106
  if (input.file_path) {
@@ -31768,8 +32258,8 @@ todos import issues ./linear.json --source linear --dry-run
31768
32258
  // src/lib/run-records.ts
31769
32259
  init_database();
31770
32260
  init_secret_redaction();
31771
- import { existsSync as existsSync17, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
31772
- import { join as join13, dirname as dirname10 } from "path";
32261
+ import { existsSync as existsSync18, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
32262
+ import { join as join14, dirname as dirname10 } from "path";
31773
32263
  var RUN_RECORD_SCHEMA = "todos.run_record.v1";
31774
32264
  var RUN_RECORD_STATUSES = ["active", "completed", "failed", "archived"];
31775
32265
  function parseJsonArray3(raw, fallback = []) {
@@ -31962,9 +32452,9 @@ function buildRunReplayBundle(id, db) {
31962
32452
  }
31963
32453
  function exportRunReplay(id, outputPath, db) {
31964
32454
  const bundle = buildRunReplayBundle(id, db);
31965
- const path = outputPath ?? join13(process.cwd(), ".todos", "replays", `${id.slice(0, 8)}.json`);
31966
- mkdirSync11(dirname10(path), { recursive: true });
31967
- writeFileSync9(path, JSON.stringify(bundle, null, 2));
32455
+ const path = outputPath ?? join14(process.cwd(), ".todos", "replays", `${id.slice(0, 8)}.json`);
32456
+ mkdirSync12(dirname10(path), { recursive: true });
32457
+ writeFileSync10(path, JSON.stringify(bundle, null, 2));
31968
32458
  const d = db || getDatabase();
31969
32459
  d.run(`UPDATE run_records SET replay_bundle = ?, updated_at = ? WHERE id = ?`, [path, now(), id]);
31970
32460
  return { path, bundle };
@@ -32002,16 +32492,16 @@ function formatRunRecordMarkdown(record) {
32002
32492
  `;
32003
32493
  }
32004
32494
  function getDefaultReplayDir() {
32005
- const local = join13(process.cwd(), ".todos", "replays");
32006
- if (existsSync17(join13(process.cwd(), ".todos")))
32495
+ const local = join14(process.cwd(), ".todos", "replays");
32496
+ if (existsSync18(join14(process.cwd(), ".todos")))
32007
32497
  return local;
32008
32498
  const home = process.env["HOME"] || "~";
32009
- return join13(home, ".hasna", "todos", "replays");
32499
+ return join14(home, ".hasna", "todos", "replays");
32010
32500
  }
32011
32501
  // src/lib/release-checks.ts
32012
32502
  init_secret_redaction();
32013
- import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
32014
- import { join as join14, relative as relative5 } from "path";
32503
+ import { existsSync as existsSync19, readFileSync as readFileSync16, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
32504
+ import { join as join15, relative as relative5 } from "path";
32015
32505
  var RELEASE_CHECK_SCHEMA = "todos.release_check.v1";
32016
32506
  var FORBIDDEN_DIST_PATTERNS = [
32017
32507
  {
@@ -32024,16 +32514,16 @@ var FORBIDDEN_DIST_PATTERNS = [
32024
32514
  ];
32025
32515
  var REQUIRED_BINS = ["todos", "todos-mcp", "todos-serve"];
32026
32516
  function readPackageJson3(root) {
32027
- const path = join14(root, "package.json");
32028
- if (!existsSync18(path))
32517
+ const path = join15(root, "package.json");
32518
+ if (!existsSync19(path))
32029
32519
  throw new Error(`package.json not found in ${root}`);
32030
- return JSON.parse(readFileSync15(path, "utf8"));
32520
+ return JSON.parse(readFileSync16(path, "utf8"));
32031
32521
  }
32032
32522
  function walkFiles(dir, acc = []) {
32033
- if (!existsSync18(dir))
32523
+ if (!existsSync19(dir))
32034
32524
  return acc;
32035
32525
  for (const entry2 of readdirSync3(dir)) {
32036
- const full = join14(dir, entry2);
32526
+ const full = join15(dir, entry2);
32037
32527
  const st = statSync5(full);
32038
32528
  if (st.isDirectory())
32039
32529
  walkFiles(full, acc);
@@ -32051,8 +32541,8 @@ function auditPackageContents(root) {
32051
32541
  checks.push({ id: "files_dist", severity: "error", message: "package.json files must include dist" });
32052
32542
  }
32053
32543
  for (const pattern of files) {
32054
- const target = join14(root, pattern);
32055
- if (!existsSync18(target)) {
32544
+ const target = join15(root, pattern);
32545
+ if (!existsSync19(target)) {
32056
32546
  checks.push({ id: `files_missing_${pattern}`, severity: "error", message: `Published file path missing: ${pattern}` });
32057
32547
  }
32058
32548
  }
@@ -32066,8 +32556,8 @@ function auditPackageContents(root) {
32066
32556
  checks.push({ id: `bin_${name}`, severity: "error", message: `Missing bin entry: ${name}` });
32067
32557
  continue;
32068
32558
  }
32069
- const binPath = join14(root, rel);
32070
- if (!existsSync18(binPath)) {
32559
+ const binPath = join15(root, rel);
32560
+ if (!existsSync19(binPath)) {
32071
32561
  checks.push({ id: `bin_path_${name}`, severity: "error", message: `Bin file missing: ${rel}` });
32072
32562
  } else {
32073
32563
  checks.push({ id: `bin_ok_${name}`, severity: "info", message: `Bin present: ${name} \u2192 ${rel}` });
@@ -32084,8 +32574,8 @@ function auditPackageContents(root) {
32084
32574
  }
32085
32575
  function scanDistArtifacts(root) {
32086
32576
  const checks = [];
32087
- const distDir = join14(root, "dist");
32088
- if (!existsSync18(distDir)) {
32577
+ const distDir = join15(root, "dist");
32578
+ if (!existsSync19(distDir)) {
32089
32579
  checks.push({ id: "dist_missing", severity: "error", message: "dist/ directory not found \u2014 run bun run build" });
32090
32580
  return checks;
32091
32581
  }
@@ -32093,7 +32583,7 @@ function scanDistArtifacts(root) {
32093
32583
  const rel = relative5(root, file);
32094
32584
  let content;
32095
32585
  try {
32096
- content = readFileSync15(file, "utf8");
32586
+ content = readFileSync16(file, "utf8");
32097
32587
  } catch {
32098
32588
  continue;
32099
32589
  }
@@ -32391,15 +32881,15 @@ function renderReleaseNotesMarkdown(document) {
32391
32881
  // src/lib/db-backup.ts
32392
32882
  init_database();
32393
32883
  init_migrations();
32394
- import { existsSync as existsSync19, copyFileSync, mkdirSync as mkdirSync12, readFileSync as readFileSync16, statSync as statSync6, writeFileSync as writeFileSync10, unlinkSync } from "fs";
32395
- import { dirname as dirname11, join as join15, resolve as resolve14 } from "path";
32884
+ import { existsSync as existsSync20, copyFileSync, mkdirSync as mkdirSync13, readFileSync as readFileSync17, statSync as statSync6, writeFileSync as writeFileSync11, unlinkSync } from "fs";
32885
+ import { dirname as dirname11, join as join16, resolve as resolve15 } from "path";
32396
32886
  import { Database as Database3 } from "bun:sqlite";
32397
32887
  var DB_BACKUP_SCHEMA = "todos.db_backup.v1";
32398
32888
  function resolveDbPath(dbPath) {
32399
32889
  if (dbPath)
32400
- return resolve14(dbPath);
32890
+ return resolve15(dbPath);
32401
32891
  if (process.env["TODOS_DB_PATH"] && process.env["TODOS_DB_PATH"] !== ":memory:") {
32402
- return resolve14(process.env["TODOS_DB_PATH"]);
32892
+ return resolve15(process.env["TODOS_DB_PATH"]);
32403
32893
  }
32404
32894
  const db = getDatabase();
32405
32895
  const filename = db.filename;
@@ -32409,9 +32899,9 @@ function resolveDbPath(dbPath) {
32409
32899
  }
32410
32900
  function backupDatabase(outputPath, sourcePath) {
32411
32901
  const source9 = resolveDbPath(sourcePath);
32412
- if (!existsSync19(source9))
32902
+ if (!existsSync20(source9))
32413
32903
  throw new Error(`Database not found: ${source9}`);
32414
- mkdirSync12(dirname11(outputPath), { recursive: true });
32904
+ mkdirSync13(dirname11(outputPath), { recursive: true });
32415
32905
  closeDatabase();
32416
32906
  const src = new Database3(source9);
32417
32907
  try {
@@ -32419,7 +32909,7 @@ function backupDatabase(outputPath, sourcePath) {
32419
32909
  } catch {}
32420
32910
  const image = src.serialize();
32421
32911
  src.close();
32422
- writeFileSync10(outputPath, image);
32912
+ writeFileSync11(outputPath, image);
32423
32913
  const method = "file_copy";
32424
32914
  const bytes = statSync6(outputPath).size;
32425
32915
  return {
@@ -32432,14 +32922,14 @@ function backupDatabase(outputPath, sourcePath) {
32432
32922
  };
32433
32923
  }
32434
32924
  function restoreDatabase(backupPath, targetPath) {
32435
- if (!existsSync19(backupPath))
32925
+ if (!existsSync20(backupPath))
32436
32926
  throw new Error(`Backup not found: ${backupPath}`);
32437
32927
  const integrity = checkDatabaseIntegrity(backupPath);
32438
32928
  if (!integrity.ok) {
32439
32929
  throw new Error(`Backup failed integrity check: ${integrity.errors.join("; ")}`);
32440
32930
  }
32441
- const target = targetPath ? resolve14(targetPath) : resolveDbPath();
32442
- mkdirSync12(dirname11(target), { recursive: true });
32931
+ const target = targetPath ? resolve15(targetPath) : resolveDbPath();
32932
+ mkdirSync13(dirname11(target), { recursive: true });
32443
32933
  const staging = `${target}.restore.tmp`;
32444
32934
  copyFileSync(backupPath, staging);
32445
32935
  copyFileSync(staging, target);
@@ -32457,9 +32947,9 @@ function restoreDatabase(backupPath, targetPath) {
32457
32947
  };
32458
32948
  }
32459
32949
  function checkDatabaseIntegrity(dbPath) {
32460
- const path = dbPath ? resolve14(dbPath) : resolveDbPath();
32950
+ const path = dbPath ? resolve15(dbPath) : resolveDbPath();
32461
32951
  const errors = [];
32462
- if (!existsSync19(path)) {
32952
+ if (!existsSync20(path)) {
32463
32953
  return {
32464
32954
  schema_version: DB_BACKUP_SCHEMA,
32465
32955
  path,
@@ -32522,7 +33012,7 @@ function checkDatabaseIntegrity(dbPath) {
32522
33012
  };
32523
33013
  }
32524
33014
  function compactDatabase(dbPath) {
32525
- const path = dbPath ? resolve14(dbPath) : resolveDbPath();
33015
+ const path = dbPath ? resolve15(dbPath) : resolveDbPath();
32526
33016
  const before = statSync6(path).size;
32527
33017
  const db = new Database3(path);
32528
33018
  db.exec("VACUUM");
@@ -32532,7 +33022,7 @@ function compactDatabase(dbPath) {
32532
33022
  return { path, bytes_before: before, bytes_after: after };
32533
33023
  }
32534
33024
  function migrationDryRun(dbPath) {
32535
- const path = dbPath ? resolve14(dbPath) : resolveDbPath();
33025
+ const path = dbPath ? resolve15(dbPath) : resolveDbPath();
32536
33026
  const db = new Database3(path, { readonly: true });
32537
33027
  let current = 0;
32538
33028
  try {
@@ -32556,16 +33046,16 @@ function migrationDryRun(dbPath) {
32556
33046
  };
32557
33047
  }
32558
33048
  function defaultBackupPath(dbPath) {
32559
- const base = dbPath ? dirname11(resolve14(dbPath)) : dirname11(resolveDbPath());
33049
+ const base = dbPath ? dirname11(resolve15(dbPath)) : dirname11(resolveDbPath());
32560
33050
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
32561
- return join15(base, "backups", `todos-${stamp}.db`);
33051
+ return join16(base, "backups", `todos-${stamp}.db`);
32562
33052
  }
32563
33053
  function readBackupManifest(backupPath) {
32564
33054
  const manifestPath = `${backupPath}.json`;
32565
- if (!existsSync19(manifestPath))
33055
+ if (!existsSync20(manifestPath))
32566
33056
  return null;
32567
33057
  try {
32568
- return JSON.parse(readFileSync16(manifestPath, "utf8"));
33058
+ return JSON.parse(readFileSync17(manifestPath, "utf8"));
32569
33059
  } catch {
32570
33060
  return null;
32571
33061
  }
@@ -32575,8 +33065,8 @@ function writeBackupManifest(backupPath, result) {
32575
33065
  writeFileSyncSafe(manifestPath, JSON.stringify(result, null, 2));
32576
33066
  }
32577
33067
  function writeFileSyncSafe(path, content) {
32578
- mkdirSync12(dirname11(path), { recursive: true });
32579
- writeFileSync10(path, content);
33068
+ mkdirSync13(dirname11(path), { recursive: true });
33069
+ writeFileSync11(path, content);
32580
33070
  }
32581
33071
  // src/lib/json-schemas.ts
32582
33072
  var JSON_SCHEMA_CATALOG_VERSION = "todos.json_schema_catalog.v1";
@@ -32639,6 +33129,7 @@ var JSON_SCHEMAS = {
32639
33129
  plan: def("plan", "todos.plan.v1", "Plan", ["schema_version", "id", "name", "status", "created_at", "updated_at"], {
32640
33130
  schema_version: { type: "string", enum: ["todos.plan.v1"] },
32641
33131
  id: { type: "string" },
33132
+ slug: { type: ["string", "null"] },
32642
33133
  project_id: { type: ["string", "null"] },
32643
33134
  name: { type: "string" },
32644
33135
  description: { type: ["string", "null"] },
@@ -33081,17 +33572,17 @@ ${SCHEMA_ENTITIES.map((e) => `- **${e}**: \`${JSON_SCHEMAS[e].schema_version}\``
33081
33572
  `;
33082
33573
  }
33083
33574
  function exportSchemasToDirectory(dir) {
33084
- const { mkdirSync: mkdirSync13, writeFileSync: writeFileSync11 } = __require("fs");
33085
- const { join: join16 } = __require("path");
33086
- mkdirSync13(dir, { recursive: true });
33575
+ const { mkdirSync: mkdirSync14, writeFileSync: writeFileSync12 } = __require("fs");
33576
+ const { join: join17 } = __require("path");
33577
+ mkdirSync14(dir, { recursive: true });
33087
33578
  const written = [];
33088
33579
  for (const entity of SCHEMA_ENTITIES) {
33089
- const path = join16(dir, `${entity}.${JSON_SCHEMAS[entity].schema_version.replace(/\./g, "-")}.json`);
33090
- writeFileSync11(path, JSON.stringify(JSON_SCHEMAS[entity], null, 2));
33580
+ const path = join17(dir, `${entity}.${JSON_SCHEMAS[entity].schema_version.replace(/\./g, "-")}.json`);
33581
+ writeFileSync12(path, JSON.stringify(JSON_SCHEMAS[entity], null, 2));
33091
33582
  written.push(path);
33092
33583
  }
33093
- const catalogPath = join16(dir, "catalog.json");
33094
- writeFileSync11(catalogPath, JSON.stringify({
33584
+ const catalogPath = join17(dir, "catalog.json");
33585
+ writeFileSync12(catalogPath, JSON.stringify({
33095
33586
  catalog_version: JSON_SCHEMA_CATALOG_VERSION,
33096
33587
  semver: SCHEMA_SEMVER,
33097
33588
  entities: listJsonSchemas(),
@@ -33990,7 +34481,7 @@ function getReminderDocs() {
33990
34481
  }
33991
34482
  // src/lib/import-export-bridge.ts
33992
34483
  init_database();
33993
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, mkdirSync as mkdirSync13 } from "fs";
34484
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync14 } from "fs";
33994
34485
  import { dirname as dirname12 } from "path";
33995
34486
  init_secret_redaction();
33996
34487
  var BUNDLE_SCHEMA = "todos.bundle.v1";
@@ -34399,11 +34890,11 @@ function importBundle(bundle, options = {}, db) {
34399
34890
  return result;
34400
34891
  }
34401
34892
  function writeBundleFile(bundle, path) {
34402
- mkdirSync13(dirname12(path), { recursive: true });
34403
- writeFileSync11(path, JSON.stringify(bundle, null, 2), "utf8");
34893
+ mkdirSync14(dirname12(path), { recursive: true });
34894
+ writeFileSync12(path, JSON.stringify(bundle, null, 2), "utf8");
34404
34895
  }
34405
34896
  function readBundleFile(path) {
34406
- const raw = JSON.parse(readFileSync17(path, "utf8"));
34897
+ const raw = JSON.parse(readFileSync18(path, "utf8"));
34407
34898
  const validation = validateBundle(raw);
34408
34899
  if (!validation.valid)
34409
34900
  throw new Error(`Invalid bundle file: ${validation.errors.join("; ")}`);
@@ -34879,7 +35370,7 @@ function createPlanWithSteps(name, steps, opts = {}, db) {
34879
35370
  }
34880
35371
  // src/lib/handoff-packets.ts
34881
35372
  init_database();
34882
- import { writeFileSync as writeFileSync12, mkdirSync as mkdirSync14 } from "fs";
35373
+ import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync15 } from "fs";
34883
35374
  import { dirname as dirname13 } from "path";
34884
35375
  var HANDOFF_PACKET_SCHEMA = "todos.handoff_packet.v1";
34885
35376
  function summarizeTask2(t) {
@@ -35054,8 +35545,8 @@ function formatHandoffPacket(packet, format = "json") {
35054
35545
  function exportHandoffPacket(input = {}, path, db) {
35055
35546
  const packet = createHandoffPacket(input, db);
35056
35547
  if (path) {
35057
- mkdirSync14(dirname13(path), { recursive: true });
35058
- writeFileSync12(path, formatHandoffPacket(packet, "json"), "utf8");
35548
+ mkdirSync15(dirname13(path), { recursive: true });
35549
+ writeFileSync13(path, formatHandoffPacket(packet, "json"), "utf8");
35059
35550
  }
35060
35551
  return packet;
35061
35552
  }
@@ -35658,8 +36149,8 @@ function generateCliReferenceMarkdown() {
35658
36149
  }
35659
36150
  // src/db/builtin-templates.ts
35660
36151
  init_database();
35661
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
35662
- import { join as join16 } from "path";
36152
+ import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync14 } from "fs";
36153
+ import { join as join17 } from "path";
35663
36154
  var BUILTIN_TEMPLATE_LIBRARY_VERSION = "2026-05-21";
35664
36155
  var BUILTIN_TEMPLATE_LIBRARY_SOURCE = "bundled-local-template-library";
35665
36156
  var TEMPLATE_LIBRARY_SCHEMA = "todos.template_library.v1";
@@ -35932,11 +36423,11 @@ function exportBuiltinTemplateFiles() {
35932
36423
  }));
35933
36424
  }
35934
36425
  function writeBuiltinTemplateFiles(directory) {
35935
- mkdirSync15(directory, { recursive: true });
36426
+ mkdirSync16(directory, { recursive: true });
35936
36427
  const files = [];
35937
36428
  for (const entry2 of exportBuiltinTemplateFiles()) {
35938
- const path = join16(directory, entry2.filename);
35939
- writeFileSync13(path, `${JSON.stringify(entry2.template, null, 2)}
36429
+ const path = join17(directory, entry2.filename);
36430
+ writeFileSync14(path, `${JSON.stringify(entry2.template, null, 2)}
35940
36431
  `, "utf-8");
35941
36432
  files.push(path);
35942
36433
  }
@@ -35979,7 +36470,7 @@ function initBuiltinTemplates(db) {
35979
36470
  }
35980
36471
  // src/lib/template-library.ts
35981
36472
  init_database();
35982
- import { writeFileSync as writeFileSync14, readFileSync as readFileSync18, mkdirSync as mkdirSync16 } from "fs";
36473
+ import { writeFileSync as writeFileSync15, readFileSync as readFileSync19, mkdirSync as mkdirSync17 } from "fs";
35983
36474
  import { dirname as dirname14 } from "path";
35984
36475
  function listTemplateLibrary(db) {
35985
36476
  const d = db || getDatabase();
@@ -36022,8 +36513,8 @@ function exportTemplateLibraryCatalog(path, db) {
36022
36513
  templates: listTemplateLibrary(db)
36023
36514
  };
36024
36515
  if (path) {
36025
- mkdirSync16(dirname14(path), { recursive: true });
36026
- writeFileSync14(path, JSON.stringify(catalog, null, 2), "utf8");
36516
+ mkdirSync17(dirname14(path), { recursive: true });
36517
+ writeFileSync15(path, JSON.stringify(catalog, null, 2), "utf8");
36027
36518
  }
36028
36519
  return catalog;
36029
36520
  }
@@ -36040,7 +36531,7 @@ function exportInstalledTemplate(name, db) {
36040
36531
  }
36041
36532
  function importTemplateFromFile(path, db) {
36042
36533
  const d = db || getDatabase();
36043
- const raw = JSON.parse(readFileSync18(path, "utf8"));
36534
+ const raw = JSON.parse(readFileSync19(path, "utf8"));
36044
36535
  const payload = raw.template ?? raw;
36045
36536
  const created = importTemplate(payload, d);
36046
36537
  return { id: created.id, name: created.name };
@@ -36218,9 +36709,9 @@ todos machines topology # full diagnostic report
36218
36709
  }
36219
36710
  // src/lib/environment-snapshots.ts
36220
36711
  import { createHash as createHash12 } from "crypto";
36221
- import { existsSync as existsSync20, readFileSync as readFileSync19, statSync as statSync7 } from "fs";
36712
+ import { existsSync as existsSync21, readFileSync as readFileSync20, statSync as statSync7 } from "fs";
36222
36713
  import { hostname as hostname3, platform, arch } from "os";
36223
- import { dirname as dirname15, join as join17, resolve as resolve15 } from "path";
36714
+ import { dirname as dirname15, join as join18, resolve as resolve16 } from "path";
36224
36715
  import { tmpdir as tmpdir3 } from "os";
36225
36716
  init_database();
36226
36717
  init_redaction();
@@ -36245,20 +36736,20 @@ function sha2565(value) {
36245
36736
  return createHash12("sha256").update(value).digest("hex");
36246
36737
  }
36247
36738
  function fileRecord(root, relativePath) {
36248
- const path = join17(root, relativePath);
36249
- if (!existsSync20(path))
36739
+ const path = join18(root, relativePath);
36740
+ if (!existsSync21(path))
36250
36741
  return null;
36251
36742
  const stat = statSync7(path);
36252
36743
  if (!stat.isFile())
36253
36744
  return null;
36254
- const content = readFileSync19(path);
36745
+ const content = readFileSync20(path);
36255
36746
  return { path: relativePath, sha256: sha2565(content), size_bytes: content.length };
36256
36747
  }
36257
36748
  function manifestRecord(root, relativePath) {
36258
36749
  const base = fileRecord(root, relativePath);
36259
36750
  if (!base)
36260
36751
  return null;
36261
- const parsed = readJsonFile(join17(root, relativePath));
36752
+ const parsed = readJsonFile(join18(root, relativePath));
36262
36753
  if (!parsed)
36263
36754
  return { ...base, redacted: {} };
36264
36755
  const redacted = redactValue({
@@ -36353,15 +36844,15 @@ function commandEnv(env, includeValues) {
36353
36844
  function defaultSnapshotDir() {
36354
36845
  const dbPath = getDatabasePath();
36355
36846
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
36356
- return join17(tmpdir3(), "hasna-todos", "environment-snapshots");
36357
- return join17(dirname15(resolve15(dbPath)), "environment-snapshots");
36847
+ return join18(tmpdir3(), "hasna-todos", "environment-snapshots");
36848
+ return join18(dirname15(resolve16(dbPath)), "environment-snapshots");
36358
36849
  }
36359
36850
  function snapshotWithId(snapshot) {
36360
36851
  const digest = sha2565(JSON.stringify(snapshot)).slice(0, 24);
36361
36852
  return { id: `env_${digest}`, ...snapshot };
36362
36853
  }
36363
36854
  function captureEnvironmentSnapshot(input = {}) {
36364
- const root = resolve15(input.root || process.cwd());
36855
+ const root = resolve16(input.root || process.cwd());
36365
36856
  const env = input.env || process.env;
36366
36857
  const warnings = [];
36367
36858
  const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
@@ -36401,13 +36892,13 @@ function captureEnvironmentSnapshot(input = {}) {
36401
36892
  });
36402
36893
  }
36403
36894
  function writeEnvironmentSnapshot(snapshot, outputPath) {
36404
- const path = outputPath ? resolve15(outputPath) : join17(defaultSnapshotDir(), `${snapshot.id}.json`);
36895
+ const path = outputPath ? resolve16(outputPath) : join18(defaultSnapshotDir(), `${snapshot.id}.json`);
36405
36896
  ensureDir2(dirname15(path));
36406
36897
  writeJsonFile(path, snapshot);
36407
36898
  return path;
36408
36899
  }
36409
36900
  function readEnvironmentSnapshot(path) {
36410
- const snapshot = readJsonFile(resolve15(path));
36901
+ const snapshot = readJsonFile(resolve16(path));
36411
36902
  if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
36412
36903
  throw new Error(`Invalid environment snapshot: ${path}`);
36413
36904
  }
@@ -36493,8 +36984,8 @@ function compareEnvironmentSnapshotFiles(leftPath, rightPath) {
36493
36984
  // src/lib/decision-records.ts
36494
36985
  init_database();
36495
36986
  import { createHash as createHash13 } from "crypto";
36496
- import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync15 } from "fs";
36497
- import { dirname as dirname16, join as join18 } from "path";
36987
+ import { mkdirSync as mkdirSync18, writeFileSync as writeFileSync16 } from "fs";
36988
+ import { dirname as dirname16, join as join19 } from "path";
36498
36989
  var DECISION_RECORD_SCHEMA = "todos.decision_record.v1";
36499
36990
  var KNOWLEDGE_SNAPSHOT_SCHEMA = "todos.knowledge_snapshot.v1";
36500
36991
  var DECISION_STATUSES = ["proposed", "accepted", "deprecated", "superseded", "rejected"];
@@ -36724,9 +37215,9 @@ function exportDecisionRecord(id, outputPath, format = "markdown", db) {
36724
37215
  if (!record)
36725
37216
  throw new Error(`Decision record not found: ${id}`);
36726
37217
  const content = format === "markdown" ? formatDecisionRecordMarkdown(record) : JSON.stringify(record, null, 2);
36727
- const path = outputPath ?? join18(process.cwd(), ".todos", "decisions", `${record.short_ref}.${format === "markdown" ? "md" : "json"}`);
36728
- mkdirSync17(dirname16(path), { recursive: true });
36729
- writeFileSync15(path, content, "utf8");
37218
+ const path = outputPath ?? join19(process.cwd(), ".todos", "decisions", `${record.short_ref}.${format === "markdown" ? "md" : "json"}`);
37219
+ mkdirSync18(dirname16(path), { recursive: true });
37220
+ writeFileSync16(path, content, "utf8");
36730
37221
  return { path, content };
36731
37222
  }
36732
37223
  function buildKnowledgeSnapshotPayload(input, db) {
@@ -36872,9 +37363,9 @@ function exportKnowledgeSnapshot(id, outputPath, format = "markdown", db) {
36872
37363
  throw new Error(`Knowledge snapshot not found: ${id}`);
36873
37364
  const content = format === "markdown" ? formatKnowledgeSnapshotMarkdown(record) : JSON.stringify(record, null, 2);
36874
37365
  const slug = record.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
36875
- const path = outputPath ?? join18(process.cwd(), ".todos", "knowledge", `${slug || record.id.slice(0, 8)}.${format === "markdown" ? "md" : "json"}`);
36876
- mkdirSync17(dirname16(path), { recursive: true });
36877
- writeFileSync15(path, content, "utf8");
37366
+ const path = outputPath ?? join19(process.cwd(), ".todos", "knowledge", `${slug || record.id.slice(0, 8)}.${format === "markdown" ? "md" : "json"}`);
37367
+ mkdirSync18(dirname16(path), { recursive: true });
37368
+ writeFileSync16(path, content, "utf8");
36878
37369
  return { path, content };
36879
37370
  }
36880
37371
  function getDecisionRecordsDocs() {
@@ -36900,7 +37391,7 @@ Schema versions:
36900
37391
  }
36901
37392
  // src/lib/report-exports.ts
36902
37393
  init_database();
36903
- import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync18 } from "fs";
37394
+ import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
36904
37395
  import { dirname as dirname17 } from "path";
36905
37396
  init_secret_redaction();
36906
37397
  var REPORT_EXPORT_SCHEMA = "todos.report_export.v1";
@@ -37133,8 +37624,8 @@ function formatReportExport(data, format) {
37133
37624
  return format === "html" ? formatReportHtml(data) : formatReportMarkdown(data);
37134
37625
  }
37135
37626
  function writeReportExport(data, format, path) {
37136
- mkdirSync18(dirname17(path), { recursive: true });
37137
- writeFileSync16(path, formatReportExport(data, format), "utf8");
37627
+ mkdirSync19(dirname17(path), { recursive: true });
37628
+ writeFileSync17(path, formatReportExport(data, format), "utf8");
37138
37629
  }
37139
37630
  function exportReport(input, db) {
37140
37631
  const data = buildReportExportData(input, db);
@@ -37167,8 +37658,8 @@ todos report export --kind retrospective --days 14 --format markdown --out retro
37167
37658
  `;
37168
37659
  }
37169
37660
  // src/lib/command-aliases.ts
37170
- import { existsSync as existsSync21, readFileSync as readFileSync20, writeFileSync as writeFileSync17, mkdirSync as mkdirSync19 } from "fs";
37171
- import { join as join19 } from "path";
37661
+ import { existsSync as existsSync22, readFileSync as readFileSync21, writeFileSync as writeFileSync18, mkdirSync as mkdirSync20 } from "fs";
37662
+ import { join as join20 } from "path";
37172
37663
  var COMMAND_ALIASES_SCHEMA = "todos.command_aliases.v1";
37173
37664
  var RESERVED = new Set([...listTopLevelCommands(), "help", "version", "alias", "shortcuts"]);
37174
37665
  var BUILTIN_SHORTCUTS = [
@@ -37187,7 +37678,7 @@ var BUILTIN_SHORTCUTS = [
37187
37678
  { pattern: /^reports?$/, argv: ["report", "docs"], explain: "Report export documentation" }
37188
37679
  ];
37189
37680
  function aliasesPath(cwd = process.cwd()) {
37190
- return join19(cwd, ".todos", "aliases.json");
37681
+ return join20(cwd, ".todos", "aliases.json");
37191
37682
  }
37192
37683
  function emptyStore() {
37193
37684
  return { schema_version: COMMAND_ALIASES_SCHEMA, aliases: {}, updated_at: new Date(0).toISOString() };
@@ -37204,9 +37695,9 @@ function validateAliasName(name) {
37204
37695
  }
37205
37696
  function loadAliasStore(cwd) {
37206
37697
  const path = aliasesPath(cwd);
37207
- if (!existsSync21(path))
37698
+ if (!existsSync22(path))
37208
37699
  return emptyStore();
37209
- const parsed = JSON.parse(readFileSync20(path, "utf8"));
37700
+ const parsed = JSON.parse(readFileSync21(path, "utf8"));
37210
37701
  if (parsed.schema_version !== COMMAND_ALIASES_SCHEMA) {
37211
37702
  throw new Error(`Unsupported alias store schema: ${parsed.schema_version}`);
37212
37703
  }
@@ -37214,9 +37705,9 @@ function loadAliasStore(cwd) {
37214
37705
  }
37215
37706
  function saveAliasStore(store, cwd) {
37216
37707
  const path = aliasesPath(cwd);
37217
- mkdirSync19(join19(path, ".."), { recursive: true });
37708
+ mkdirSync20(join20(path, ".."), { recursive: true });
37218
37709
  store.updated_at = new Date().toISOString();
37219
- writeFileSync17(path, JSON.stringify(store, null, 2), "utf8");
37710
+ writeFileSync18(path, JSON.stringify(store, null, 2), "utf8");
37220
37711
  }
37221
37712
  function parseArgv(command) {
37222
37713
  const argv = [];
@@ -37862,18 +38353,18 @@ function createBranchWorkPlan(input, db) {
37862
38353
  }
37863
38354
  // src/lib/user-scaffolds.ts
37864
38355
  init_database();
37865
- import { existsSync as existsSync22, readFileSync as readFileSync21, writeFileSync as writeFileSync18, mkdirSync as mkdirSync20 } from "fs";
37866
- import { join as join20 } from "path";
38356
+ import { existsSync as existsSync23, readFileSync as readFileSync22, writeFileSync as writeFileSync19, mkdirSync as mkdirSync21 } from "fs";
38357
+ import { join as join21 } from "path";
37867
38358
  var USER_SCAFFOLD_SCHEMA = "todos.user_scaffold.v1";
37868
38359
  var SCAFFOLD_KINDS = ["task", "project", "plan", "checklist", "contract", "verification_policy"];
37869
38360
  function storeDir(cwd = process.cwd()) {
37870
- return join20(cwd, ".todos", "scaffolds");
38361
+ return join21(cwd, ".todos", "scaffolds");
37871
38362
  }
37872
38363
  function storePath(cwd) {
37873
- return join20(storeDir(cwd), "store.json");
38364
+ return join21(storeDir(cwd), "store.json");
37874
38365
  }
37875
38366
  function versionsDir(cwd) {
37876
- return join20(storeDir(cwd), "versions");
38367
+ return join21(storeDir(cwd), "versions");
37877
38368
  }
37878
38369
  function slugify5(name) {
37879
38370
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
@@ -37883,23 +38374,23 @@ function emptyStore2() {
37883
38374
  }
37884
38375
  function loadUserScaffoldStore(cwd) {
37885
38376
  const path = storePath(cwd);
37886
- if (!existsSync22(path))
38377
+ if (!existsSync23(path))
37887
38378
  return emptyStore2();
37888
- const parsed = JSON.parse(readFileSync21(path, "utf8"));
38379
+ const parsed = JSON.parse(readFileSync22(path, "utf8"));
37889
38380
  if (parsed.schema_version !== USER_SCAFFOLD_SCHEMA) {
37890
38381
  throw new Error(`Unsupported scaffold store schema: ${parsed.schema_version}`);
37891
38382
  }
37892
38383
  return parsed;
37893
38384
  }
37894
38385
  function saveUserScaffoldStore(store, cwd) {
37895
- mkdirSync20(storeDir(cwd), { recursive: true });
38386
+ mkdirSync21(storeDir(cwd), { recursive: true });
37896
38387
  store.updated_at = now();
37897
- writeFileSync18(storePath(cwd), JSON.stringify(store, null, 2), "utf8");
38388
+ writeFileSync19(storePath(cwd), JSON.stringify(store, null, 2), "utf8");
37898
38389
  }
37899
38390
  function snapshotVersion(scaffold, cwd) {
37900
- mkdirSync20(versionsDir(cwd), { recursive: true });
37901
- const path = join20(versionsDir(cwd), `${scaffold.id}-v${scaffold.version}.json`);
37902
- writeFileSync18(path, JSON.stringify(scaffold, null, 2), "utf8");
38391
+ mkdirSync21(versionsDir(cwd), { recursive: true });
38392
+ const path = join21(versionsDir(cwd), `${scaffold.id}-v${scaffold.version}.json`);
38393
+ writeFileSync19(path, JSON.stringify(scaffold, null, 2), "utf8");
37903
38394
  }
37904
38395
  function listUserScaffolds(kind, cwd) {
37905
38396
  const store = loadUserScaffoldStore(cwd);
@@ -38145,7 +38636,7 @@ function listLinkedTemplates(db, cwd) {
38145
38636
  // src/lib/agent-workflow-demo.ts
38146
38637
  init_database();
38147
38638
  import { mkdtempSync } from "fs";
38148
- import { join as join21 } from "path";
38639
+ import { join as join22 } from "path";
38149
38640
  import { tmpdir as tmpdir4 } from "os";
38150
38641
  var AGENT_WORKFLOW_DEMO_SCHEMA = "todos.agent_workflow_demo.v1";
38151
38642
  var DEMO_DEFAULT_AGENT = "demoagent";
@@ -38161,7 +38652,7 @@ function setupEphemeralDemoDb(options = {}) {
38161
38652
  if (options.db_path) {
38162
38653
  db_path = options.db_path;
38163
38654
  } else if (options.persist) {
38164
- db_path = join21(mkdtempSync(join21(tmpdir4(), "todos-demo-")), "todos.db");
38655
+ db_path = join22(mkdtempSync(join22(tmpdir4(), "todos-demo-")), "todos.db");
38165
38656
  } else {
38166
38657
  db_path = ":memory:";
38167
38658
  }
@@ -40486,18 +40977,18 @@ function runSearchView(idOrName, db) {
40486
40977
  return { ...runSavedSearch(view.filters, view.scope, d), view };
40487
40978
  }
40488
40979
  // src/lib/claude-tasks.ts
40489
- import { existsSync as existsSync23, readFileSync as readFileSync22, readdirSync as readdirSync4, writeFileSync as writeFileSync19 } from "fs";
40490
- import { join as join22 } from "path";
40980
+ import { existsSync as existsSync24, readFileSync as readFileSync23, readdirSync as readdirSync4, writeFileSync as writeFileSync20 } from "fs";
40981
+ import { join as join23 } from "path";
40491
40982
  init_config();
40492
40983
  init_sync_utils();
40493
40984
  function getTaskListDir(taskListId) {
40494
- return join22(HOME, ".claude", "tasks", taskListId);
40985
+ return join23(HOME, ".claude", "tasks", taskListId);
40495
40986
  }
40496
40987
  function readClaudeTask(dir, filename) {
40497
- return readJsonFile(join22(dir, filename));
40988
+ return readJsonFile(join23(dir, filename));
40498
40989
  }
40499
40990
  function writeClaudeTask(dir, task2) {
40500
- writeJsonFile(join22(dir, `${task2.id}.json`), task2);
40991
+ writeJsonFile(join23(dir, `${task2.id}.json`), task2);
40501
40992
  }
40502
40993
  function toClaudeStatus(status) {
40503
40994
  if (status === "pending" || status === "in_progress" || status === "completed") {
@@ -40509,14 +41000,14 @@ function toSqliteStatus(status) {
40509
41000
  return status;
40510
41001
  }
40511
41002
  function readPrefixCounter(dir) {
40512
- const path = join22(dir, ".prefix-counter");
40513
- if (!existsSync23(path))
41003
+ const path = join23(dir, ".prefix-counter");
41004
+ if (!existsSync24(path))
40514
41005
  return 0;
40515
- const val = parseInt(readFileSync22(path, "utf-8").trim(), 10);
41006
+ const val = parseInt(readFileSync23(path, "utf-8").trim(), 10);
40516
41007
  return isNaN(val) ? 0 : val;
40517
41008
  }
40518
41009
  function writePrefixCounter(dir, value) {
40519
- writeFileSync19(join22(dir, ".prefix-counter"), String(value));
41010
+ writeFileSync20(join23(dir, ".prefix-counter"), String(value));
40520
41011
  }
40521
41012
  function formatPrefixedSubject(title, prefix, counter) {
40522
41013
  const padded = String(counter).padStart(5, "0");
@@ -40543,7 +41034,7 @@ function taskToClaudeTask(task2, claudeTaskId, existingMeta) {
40543
41034
  }
40544
41035
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
40545
41036
  const dir = getTaskListDir(taskListId);
40546
- if (!existsSync23(dir))
41037
+ if (!existsSync24(dir))
40547
41038
  ensureDir2(dir);
40548
41039
  const filter = {};
40549
41040
  if (projectId)
@@ -40552,7 +41043,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
40552
41043
  const existingByTodosId = new Map;
40553
41044
  const files = listJsonFiles(dir);
40554
41045
  for (const f of files) {
40555
- const path = join22(dir, f);
41046
+ const path = join23(dir, f);
40556
41047
  const ct = readClaudeTask(dir, f);
40557
41048
  if (ct?.metadata?.["todos_id"]) {
40558
41049
  existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
@@ -40639,7 +41130,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
40639
41130
  }
40640
41131
  function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
40641
41132
  const dir = getTaskListDir(taskListId);
40642
- if (!existsSync23(dir)) {
41133
+ if (!existsSync24(dir)) {
40643
41134
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
40644
41135
  }
40645
41136
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
@@ -40659,7 +41150,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
40659
41150
  }
40660
41151
  for (const f of files) {
40661
41152
  try {
40662
- const filePath = join22(dir, f);
41153
+ const filePath = join23(dir, f);
40663
41154
  const ct = readClaudeTask(dir, f);
40664
41155
  if (!ct)
40665
41156
  continue;
@@ -40727,22 +41218,22 @@ function syncClaudeTaskList(taskListId, projectId, options = {}) {
40727
41218
  }
40728
41219
 
40729
41220
  // src/lib/agent-tasks.ts
40730
- import { existsSync as existsSync24 } from "fs";
40731
- import { join as join23 } from "path";
41221
+ import { existsSync as existsSync25 } from "fs";
41222
+ import { join as join24 } from "path";
40732
41223
  init_sync_utils();
40733
41224
  init_config();
40734
41225
  function agentBaseDir(agent) {
40735
41226
  const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
40736
- return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join23(getTodosGlobalDir(), "agents");
41227
+ return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join24(getTodosGlobalDir(), "agents");
40737
41228
  }
40738
41229
  function getTaskListDir2(agent, taskListId) {
40739
- return join23(agentBaseDir(agent), agent, taskListId);
41230
+ return join24(agentBaseDir(agent), agent, taskListId);
40740
41231
  }
40741
41232
  function readAgentTask(dir, filename) {
40742
- return readJsonFile(join23(dir, filename));
41233
+ return readJsonFile(join24(dir, filename));
40743
41234
  }
40744
41235
  function writeAgentTask(dir, task2) {
40745
- writeJsonFile(join23(dir, `${task2.id}.json`), task2);
41236
+ writeJsonFile(join24(dir, `${task2.id}.json`), task2);
40746
41237
  }
40747
41238
  function taskToAgentTask(task2, externalId, existingMeta) {
40748
41239
  return {
@@ -40767,7 +41258,7 @@ function metadataKey(agent) {
40767
41258
  }
40768
41259
  function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
40769
41260
  const dir = getTaskListDir2(agent, taskListId);
40770
- if (!existsSync24(dir))
41261
+ if (!existsSync25(dir))
40771
41262
  ensureDir2(dir);
40772
41263
  const filter = {};
40773
41264
  if (projectId)
@@ -40776,7 +41267,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
40776
41267
  const existingByTodosId = new Map;
40777
41268
  const files = listJsonFiles(dir);
40778
41269
  for (const f of files) {
40779
- const path = join23(dir, f);
41270
+ const path = join24(dir, f);
40780
41271
  const at = readAgentTask(dir, f);
40781
41272
  if (at?.metadata?.["todos_id"]) {
40782
41273
  existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
@@ -40850,7 +41341,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
40850
41341
  }
40851
41342
  function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
40852
41343
  const dir = getTaskListDir2(agent, taskListId);
40853
- if (!existsSync24(dir)) {
41344
+ if (!existsSync25(dir)) {
40854
41345
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
40855
41346
  }
40856
41347
  const files = listJsonFiles(dir);
@@ -40869,7 +41360,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
40869
41360
  }
40870
41361
  for (const f of files) {
40871
41362
  try {
40872
- const filePath = join23(dir, f);
41363
+ const filePath = join24(dir, f);
40873
41364
  const at = readAgentTask(dir, f);
40874
41365
  if (!at)
40875
41366
  continue;
@@ -41007,9 +41498,9 @@ function syncWithAgents(agents, taskListIdByAgent, projectId, direction = "both"
41007
41498
  return { pushed, pulled, errors };
41008
41499
  }
41009
41500
  // src/lib/extract.ts
41010
- import { existsSync as existsSync25, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
41501
+ import { existsSync as existsSync26, readFileSync as readFileSync24, statSync as statSync8 } from "fs";
41011
41502
  import { createHash as createHash14 } from "crypto";
41012
- import { relative as relative6, resolve as resolve16, join as join24 } from "path";
41503
+ import { relative as relative6, resolve as resolve17, join as join25 } from "path";
41013
41504
  var EXTRACT_TAGS = ["TODO", "FIXME", "HACK", "XXX", "BUG", "NOTE"];
41014
41505
  var DEFAULT_EXTENSIONS = new Set([
41015
41506
  ".ts",
@@ -41079,12 +41570,12 @@ function normalizePathForMatch(value) {
41079
41570
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
41080
41571
  }
41081
41572
  function readGitignorePatterns(basePath) {
41082
- const root = statSync8(basePath).isFile() ? resolve16(basePath, "..") : basePath;
41083
- const gitignorePath = join24(root, ".gitignore");
41084
- if (!existsSync25(gitignorePath))
41573
+ const root = statSync8(basePath).isFile() ? resolve17(basePath, "..") : basePath;
41574
+ const gitignorePath = join25(root, ".gitignore");
41575
+ if (!existsSync26(gitignorePath))
41085
41576
  return [];
41086
41577
  try {
41087
- return readFileSync23(gitignorePath, "utf-8").split(`
41578
+ return readFileSync24(gitignorePath, "utf-8").split(`
41088
41579
  `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
41089
41580
  } catch {
41090
41581
  return [];
@@ -41215,7 +41706,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
41215
41706
  return files.sort();
41216
41707
  }
41217
41708
  function buildCodebaseIndex(options) {
41218
- const basePath = resolve16(options.path);
41709
+ const basePath = resolve17(options.path);
41219
41710
  const tags = options.patterns || [...EXTRACT_TAGS];
41220
41711
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
41221
41712
  const excludes = options.exclude || [];
@@ -41223,10 +41714,10 @@ function buildCodebaseIndex(options) {
41223
41714
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
41224
41715
  const indexed = [];
41225
41716
  for (const file of files) {
41226
- const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
41717
+ const fullPath = statSync8(basePath).isFile() ? basePath : join25(basePath, file);
41227
41718
  try {
41228
- const source9 = readFileSync23(fullPath, "utf-8");
41229
- const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
41719
+ const source9 = readFileSync24(fullPath, "utf-8");
41720
+ const relPath = statSync8(basePath).isFile() ? relative6(resolve17(basePath, ".."), fullPath) : file;
41230
41721
  indexed.push({
41231
41722
  file: relPath,
41232
41723
  checksum: stableHash(source9).slice(0, 24),
@@ -41246,7 +41737,7 @@ function buildCodebaseIndex(options) {
41246
41737
  };
41247
41738
  }
41248
41739
  function extractTodos(options, db) {
41249
- const basePath = resolve16(options.path);
41740
+ const basePath = resolve17(options.path);
41250
41741
  const tags = options.patterns || [...EXTRACT_TAGS];
41251
41742
  const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
41252
41743
  const excludes = options.exclude || [];
@@ -41254,10 +41745,10 @@ function extractTodos(options, db) {
41254
41745
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
41255
41746
  const allComments = [];
41256
41747
  for (const file of files) {
41257
- const fullPath = statSync8(basePath).isFile() ? basePath : join24(basePath, file);
41748
+ const fullPath = statSync8(basePath).isFile() ? basePath : join25(basePath, file);
41258
41749
  try {
41259
- const source9 = readFileSync23(fullPath, "utf-8");
41260
- const relPath = statSync8(basePath).isFile() ? relative6(resolve16(basePath, ".."), fullPath) : file;
41750
+ const source9 = readFileSync24(fullPath, "utf-8");
41751
+ const relPath = statSync8(basePath).isFile() ? relative6(resolve17(basePath, ".."), fullPath) : file;
41261
41752
  const comments = extractFromSource(source9, relPath, tags);
41262
41753
  allComments.push(...comments);
41263
41754
  } catch {}
@@ -41351,7 +41842,7 @@ async function watchSourceTodos(options, onRun) {
41351
41842
  const interval = Math.max(100, options.interval_ms || 2000);
41352
41843
  const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
41353
41844
  const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
41354
- const root = resolve16(options.path);
41845
+ const root = resolve17(options.path);
41355
41846
  const runs = [];
41356
41847
  let previous = new Map;
41357
41848
  for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
@@ -41962,7 +42453,7 @@ function renderWorkflowStatesMarkdown(states = listWorkflowStates()) {
41962
42453
  // src/lib/agent-replay-simulator.ts
41963
42454
  init_redaction();
41964
42455
  import { createHash as createHash15 } from "crypto";
41965
- import { readFileSync as readFileSync24 } from "fs";
42456
+ import { readFileSync as readFileSync25 } from "fs";
41966
42457
  function isObject(value) {
41967
42458
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
41968
42459
  }
@@ -42195,7 +42686,7 @@ function simulateAgentReplay(input, options = {}) {
42195
42686
  };
42196
42687
  }
42197
42688
  function simulateAgentReplayFile(path, options = {}) {
42198
- const parsed = JSON.parse(readFileSync24(path, "utf8"));
42689
+ const parsed = JSON.parse(readFileSync25(path, "utf8"));
42199
42690
  return simulateAgentReplay(parsed, options);
42200
42691
  }
42201
42692
  function renderAgentReplaySimulationMarkdown(simulation) {
@@ -42223,8 +42714,8 @@ function renderAgentReplaySimulationMarkdown(simulation) {
42223
42714
  // src/lib/local-extensions.ts
42224
42715
  init_config();
42225
42716
  import { createHash as createHash16, createVerify } from "crypto";
42226
- import { existsSync as existsSync26, readdirSync as readdirSync5, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
42227
- import { basename as basename5, join as join25, resolve as resolve17 } from "path";
42717
+ import { existsSync as existsSync27, readdirSync as readdirSync5, readFileSync as readFileSync26, statSync as statSync9 } from "fs";
42718
+ import { basename as basename5, join as join26, resolve as resolve18 } from "path";
42228
42719
  init_redaction();
42229
42720
  function isObject2(value) {
42230
42721
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -42306,7 +42797,7 @@ function normalizeManifest(input) {
42306
42797
  };
42307
42798
  }
42308
42799
  function parseJson(path) {
42309
- return JSON.parse(readFileSync25(path, "utf8"));
42800
+ return JSON.parse(readFileSync26(path, "utf8"));
42310
42801
  }
42311
42802
  function sha2566(bytes) {
42312
42803
  return `sha256:${createHash16("sha256").update(bytes).digest("hex")}`;
@@ -42504,14 +42995,14 @@ function verifyExtensionSignature(input) {
42504
42995
  return verifier.verify(input.public_key, decodeSignature(input.signature));
42505
42996
  }
42506
42997
  function inspectExtensionSource(source9) {
42507
- const resolved = resolve17(source9);
42508
- if (!existsSync26(resolved))
42998
+ const resolved = resolve18(source9);
42999
+ if (!existsSync27(resolved))
42509
43000
  throw new Error(`extension source not found: ${source9}`);
42510
43001
  const stat = statSync9(resolved);
42511
- const manifestPath = stat.isDirectory() ? [join25(resolved, "todos.extension.json"), join25(resolved, "extension.json")].find(existsSync26) : resolved;
43002
+ const manifestPath = stat.isDirectory() ? [join26(resolved, "todos.extension.json"), join26(resolved, "extension.json")].find(existsSync27) : resolved;
42512
43003
  if (!manifestPath)
42513
43004
  throw new Error(`extension directory ${source9} is missing todos.extension.json`);
42514
- const raw = readFileSync25(manifestPath);
43005
+ const raw = readFileSync26(manifestPath);
42515
43006
  const parsed = parseJson(manifestPath);
42516
43007
  const bundle = isObject2(parsed) && isObject2(parsed["manifest"]);
42517
43008
  const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
@@ -42602,26 +43093,26 @@ function testExtensionCompatibility(sourceOrManifest) {
42602
43093
  function projectExtensionSources(projectPath) {
42603
43094
  if (!projectPath)
42604
43095
  return [];
42605
- const root = resolve17(projectPath);
43096
+ const root = resolve18(projectPath);
42606
43097
  const candidates = [
42607
- join25(root, "todos.extension.json"),
42608
- join25(root, ".todos", "todos.extension.json")
43098
+ join26(root, "todos.extension.json"),
43099
+ join26(root, ".todos", "todos.extension.json")
42609
43100
  ];
42610
- const extensionDir = join25(root, ".todos", "extensions");
42611
- if (existsSync26(extensionDir)) {
43101
+ const extensionDir = join26(root, ".todos", "extensions");
43102
+ if (existsSync27(extensionDir)) {
42612
43103
  for (const entry2 of readdirSync5(extensionDir)) {
42613
43104
  if (entry2.startsWith("."))
42614
43105
  continue;
42615
- const full = join25(extensionDir, entry2);
43106
+ const full = join26(extensionDir, entry2);
42616
43107
  if (statSync9(full).isDirectory() || entry2.endsWith(".json"))
42617
43108
  candidates.push(full);
42618
43109
  }
42619
43110
  }
42620
- return candidates.filter(existsSync26);
43111
+ return candidates.filter(existsSync27);
42621
43112
  }
42622
43113
  function discoverLocalExtensions(options = {}) {
42623
43114
  const config = loadConfig();
42624
- const projectPath = options.project_path ? resolve17(options.project_path) : null;
43115
+ const projectPath = options.project_path ? resolve18(options.project_path) : null;
42625
43116
  const configuredSources = [
42626
43117
  ...config.extension_sources || [],
42627
43118
  ...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
@@ -42629,7 +43120,7 @@ function discoverLocalExtensions(options = {}) {
42629
43120
  const sources = Array.from(new Set([
42630
43121
  ...configuredSources,
42631
43122
  ...projectExtensionSources(projectPath || undefined)
42632
- ])).map((source9) => projectPath && !source9.startsWith("/") ? resolve17(projectPath, source9) : resolve17(source9));
43123
+ ])).map((source9) => projectPath && !source9.startsWith("/") ? resolve18(projectPath, source9) : resolve18(source9));
42633
43124
  const warnings = [];
42634
43125
  const discovered = [];
42635
43126
  for (const source9 of sources) {
@@ -43478,7 +43969,7 @@ function resolveMissingTaskFindings(input, db) {
43478
43969
  init_redaction();
43479
43970
 
43480
43971
  // src/lib/retention-cleanup.ts
43481
- import { existsSync as existsSync27, unlinkSync as unlinkSync2 } from "fs";
43972
+ import { existsSync as existsSync28, unlinkSync as unlinkSync2 } from "fs";
43482
43973
  init_database();
43483
43974
  var RETENTION_CLEANUP_CONFIRMATION = "delete-local-retention-data";
43484
43975
  var ALL_SCOPES = ["comments", "runs", "verifications", "expired_artifacts"];
@@ -43690,7 +44181,7 @@ function applyRetentionCleanup(input, db) {
43690
44181
  for (const artifact of report.candidates.artifact_files) {
43691
44182
  try {
43692
44183
  const path = artifactStorePath(artifact.relative_path);
43693
- if (!existsSync27(path)) {
44184
+ if (!existsSync28(path)) {
43694
44185
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
43695
44186
  continue;
43696
44187
  }
@@ -43928,8 +44419,8 @@ function renderScalePerformanceReportMarkdown(report) {
43928
44419
  init_database();
43929
44420
  init_migrations();
43930
44421
  init_schema();
43931
- import { chmodSync, copyFileSync as copyFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync21, statSync as statSync10 } from "fs";
43932
- import { basename as basename6, dirname as dirname18, join as join26 } from "path";
44422
+ import { chmodSync, copyFileSync as copyFileSync2, existsSync as existsSync29, mkdirSync as mkdirSync22, statSync as statSync10 } from "fs";
44423
+ import { basename as basename6, dirname as dirname18, join as join27 } from "path";
43933
44424
  var REQUIRED_TABLES2 = [
43934
44425
  "_migrations",
43935
44426
  "projects",
@@ -44036,7 +44527,7 @@ function findMissingProjectRoots(db) {
44036
44527
  continue;
44037
44528
  if (!row.path.startsWith("/"))
44038
44529
  continue;
44039
- if (!existsSync28(row.path))
44530
+ if (!existsSync29(row.path))
44040
44531
  missing++;
44041
44532
  }
44042
44533
  return missing;
@@ -44096,16 +44587,16 @@ function databasePermissionsAreUnsafe(dbPath) {
44096
44587
  function createBackup(dbPath) {
44097
44588
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
44098
44589
  return;
44099
- if (!existsSync28(dbPath))
44590
+ if (!existsSync29(dbPath))
44100
44591
  return;
44101
44592
  const stamp = now().replace(/[:.]/g, "-");
44102
- const backupDir = join26(dirname18(dbPath), `${basename6(dbPath)}.backup-${stamp}`);
44593
+ const backupDir = join27(dirname18(dbPath), `${basename6(dbPath)}.backup-${stamp}`);
44103
44594
  const files = [];
44104
- mkdirSync21(backupDir, { recursive: true });
44595
+ mkdirSync22(backupDir, { recursive: true });
44105
44596
  for (const source9 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
44106
- if (!existsSync28(source9))
44597
+ if (!existsSync29(source9))
44107
44598
  continue;
44108
- const target = join26(backupDir, basename6(source9));
44599
+ const target = join27(backupDir, basename6(source9));
44109
44600
  copyFileSync2(source9, target);
44110
44601
  files.push(target);
44111
44602
  }
@@ -44769,6 +45260,7 @@ export {
44769
45260
  writeVerificationExport,
44770
45261
  writeSdkIntegrationFixtures,
44771
45262
  writeReportExport,
45263
+ writePlanArtifact,
44772
45264
  writeOnboardingFixtureFiles,
44773
45265
  writeLocalBackupFile,
44774
45266
  writeEnvironmentSnapshot,
@@ -44935,6 +45427,8 @@ export {
44935
45427
  resolveTaskRunId,
44936
45428
  resolvePlanRef,
44937
45429
  resolvePlanId,
45430
+ resolvePlanArtifactProject,
45431
+ resolvePlanArtifactPaths,
44938
45432
  resolvePartialId,
44939
45433
  resolveMissingTaskFindings,
44940
45434
  resolveMentions,
@@ -44965,6 +45459,7 @@ export {
44965
45459
  renderReleaseNotesMarkdown,
44966
45460
  renderReleaseCompatibilityMarkdown,
44967
45461
  renderPlanningForecastMarkdown,
45462
+ renderPlanArtifactMarkdown,
44968
45463
  renderLocalUsageLedgerMarkdown,
44969
45464
  renderLocalSnapshotMarkdown,
44970
45465
  renderLocalReportMarkdown,
@@ -45022,6 +45517,7 @@ export {
45022
45517
  recordFilesTouched,
45023
45518
  recordEnvironmentSnapshot,
45024
45519
  readTesterIssueReportsPayload,
45520
+ readPlanArtifact,
45025
45521
  readLocalBackupFile,
45026
45522
  readEnvironmentSnapshot,
45027
45523
  readBundleFile,
@@ -45051,6 +45547,7 @@ export {
45051
45547
  parseStorageMode,
45052
45548
  parseRecurrenceRule,
45053
45549
  parseQuietHours,
45550
+ parsePlanArtifactMarkdown,
45054
45551
  parseNaturalLanguageTask,
45055
45552
  parseIssueExport,
45056
45553
  parseGoalCommand,
@@ -45202,6 +45699,7 @@ export {
45202
45699
  isAgentConflict,
45203
45700
  installTemplateLibrary,
45204
45701
  installLocalExtension,
45702
+ inspectPlanArtifact,
45205
45703
  inspectGitCommit,
45206
45704
  inspectExtensionSource,
45207
45705
  initBuiltinTemplates,
@@ -45675,6 +46173,7 @@ export {
45675
46173
  buildRunReplayBundle,
45676
46174
  buildResourceSnapshot,
45677
46175
  buildReportExportData,
46176
+ buildPlanArtifactSnapshot,
45678
46177
  buildMcpToolGroups,
45679
46178
  buildMachineTopologyReport,
45680
46179
  buildKnowledgeSnapshotPayload,
@@ -45800,6 +46299,7 @@ export {
45800
46299
  ProjectNotFoundError,
45801
46300
  PlanNotFoundError,
45802
46301
  PLAN_STATUSES,
46302
+ PLAN_MARKDOWN_SCHEMA,
45803
46303
  PLAN_EXECUTION_SCHEMA,
45804
46304
  PLAN_EXECUTION_MODES,
45805
46305
  PARITY_SCHEMA_VERSION,