@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/cli/commands/plan-template-commands.d.ts.map +1 -1
- package/dist/cli/index.js +862 -264
- package/dist/contracts.js +135 -5
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/plans.d.ts +8 -0
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +738 -238
- package/dist/lib/json-schemas.d.ts.map +1 -1
- package/dist/lib/local-bridge.d.ts.map +1 -1
- package/dist/lib/onboarding-fixtures.d.ts.map +1 -1
- package/dist/lib/plan-artifacts.d.ts +72 -0
- package/dist/lib/plan-artifacts.d.ts.map +1 -0
- package/dist/mcp/index.js +138 -5
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/registry.js +135 -5
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +140 -7
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/storage.js +153 -7
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1198,6 +1198,11 @@ var init_migrations = __esm(() => {
|
|
|
1198
1198
|
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
|
|
1199
1199
|
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
|
|
1200
1200
|
INSERT OR IGNORE INTO _migrations (id) VALUES (63);
|
|
1201
|
+
`,
|
|
1202
|
+
`
|
|
1203
|
+
ALTER TABLE plans ADD COLUMN slug TEXT;
|
|
1204
|
+
CREATE INDEX IF NOT EXISTS idx_plans_slug ON plans(slug);
|
|
1205
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (64);
|
|
1201
1206
|
`
|
|
1202
1207
|
];
|
|
1203
1208
|
});
|
|
@@ -1221,6 +1226,29 @@ function runMigrations(db) {
|
|
|
1221
1226
|
}
|
|
1222
1227
|
ensureSchema(db);
|
|
1223
1228
|
}
|
|
1229
|
+
function planSlugBase(value) {
|
|
1230
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "plan";
|
|
1231
|
+
}
|
|
1232
|
+
function backfillPlanSlugs(db) {
|
|
1233
|
+
try {
|
|
1234
|
+
const rows = db.query("SELECT id, project_id, name, slug FROM plans ORDER BY created_at ASC, id ASC").all();
|
|
1235
|
+
const used = new Set;
|
|
1236
|
+
for (const row of rows) {
|
|
1237
|
+
const scope = row.project_id ?? "__global__";
|
|
1238
|
+
const base = planSlugBase(row.slug || row.name);
|
|
1239
|
+
let candidate = base;
|
|
1240
|
+
let suffix = 2;
|
|
1241
|
+
while (used.has(`${scope}:${candidate}`)) {
|
|
1242
|
+
candidate = `${base}-${suffix}`;
|
|
1243
|
+
suffix += 1;
|
|
1244
|
+
}
|
|
1245
|
+
used.add(`${scope}:${candidate}`);
|
|
1246
|
+
if (row.slug !== candidate) {
|
|
1247
|
+
db.run("UPDATE plans SET slug = ? WHERE id = ?", [candidate, row.id]);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
} catch {}
|
|
1251
|
+
}
|
|
1224
1252
|
function ensureSchema(db) {
|
|
1225
1253
|
const ensureColumn = (table, column, type) => {
|
|
1226
1254
|
try {
|
|
@@ -1270,7 +1298,8 @@ function ensureSchema(db) {
|
|
|
1270
1298
|
)`);
|
|
1271
1299
|
ensureTable("plans", `
|
|
1272
1300
|
CREATE TABLE plans (
|
|
1273
|
-
id TEXT PRIMARY KEY,
|
|
1301
|
+
id TEXT PRIMARY KEY, slug TEXT,
|
|
1302
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
1274
1303
|
task_list_id TEXT, agent_id TEXT,
|
|
1275
1304
|
name TEXT NOT NULL, description TEXT,
|
|
1276
1305
|
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
|
|
@@ -1785,8 +1814,10 @@ function ensureSchema(db) {
|
|
|
1785
1814
|
ensureColumn("agents", "org_id", "TEXT");
|
|
1786
1815
|
ensureColumn("agents", "capabilities", "TEXT DEFAULT '[]'");
|
|
1787
1816
|
ensureColumn("projects", "org_id", "TEXT");
|
|
1817
|
+
ensureColumn("plans", "slug", "TEXT");
|
|
1788
1818
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
1789
1819
|
ensureColumn("plans", "agent_id", "TEXT");
|
|
1820
|
+
backfillPlanSlugs(db);
|
|
1790
1821
|
ensureColumn("task_templates", "variables", "TEXT DEFAULT '[]'");
|
|
1791
1822
|
ensureColumn("task_templates", "version", "INTEGER NOT NULL DEFAULT 1");
|
|
1792
1823
|
ensureColumn("template_tasks", "condition", "TEXT");
|
|
@@ -1901,6 +1932,8 @@ function ensureSchema(db) {
|
|
|
1901
1932
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name)");
|
|
1902
1933
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
|
|
1903
1934
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
|
|
1935
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_slug ON plans(slug)");
|
|
1936
|
+
ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_scope_slug ON plans(COALESCE(project_id, ''), slug) WHERE slug IS NOT NULL");
|
|
1904
1937
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
|
|
1905
1938
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
|
|
1906
1939
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
|
|
@@ -2734,6 +2767,9 @@ function clearExpiredLocks(db) {
|
|
|
2734
2767
|
const cutoff = lockExpiryCutoff();
|
|
2735
2768
|
db.run("UPDATE tasks SET locked_by = NULL, locked_at = NULL WHERE locked_at IS NOT NULL AND locked_at < ?", [cutoff]);
|
|
2736
2769
|
}
|
|
2770
|
+
function slugifyRef(value) {
|
|
2771
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2772
|
+
}
|
|
2737
2773
|
function resolvePartialId(db, table, partialId) {
|
|
2738
2774
|
if (!ALLOWED_TABLES.has(table)) {
|
|
2739
2775
|
throw new Error(`Invalid table name: ${table}`);
|
|
@@ -2760,6 +2796,16 @@ function resolvePartialId(db, table, partialId) {
|
|
|
2760
2796
|
if (slugRow)
|
|
2761
2797
|
return slugRow.id;
|
|
2762
2798
|
}
|
|
2799
|
+
if (table === "plans") {
|
|
2800
|
+
const slug = slugifyRef(partialId);
|
|
2801
|
+
if (slug) {
|
|
2802
|
+
const slugRows = db.query("SELECT id FROM plans WHERE slug = ?").all(slug);
|
|
2803
|
+
if (slugRows.length === 1)
|
|
2804
|
+
return slugRows[0].id;
|
|
2805
|
+
if (slugRows.length > 1)
|
|
2806
|
+
return null;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2763
2809
|
if (table === "projects") {
|
|
2764
2810
|
const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
2765
2811
|
if (nameRow)
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
25993
|
-
import { basename as basename2, dirname as dirname7, resolve as
|
|
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 =
|
|
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 (
|
|
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 =
|
|
26023
|
-
if (!
|
|
26512
|
+
const file = resolve11(path, "package.json");
|
|
26513
|
+
if (!existsSync10(file))
|
|
26024
26514
|
return null;
|
|
26025
26515
|
try {
|
|
26026
|
-
const parsed = JSON.parse(
|
|
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 (
|
|
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
|
|
26539
|
-
import { join as
|
|
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
|
|
27035
|
+
return join11(getConfigDir(), "config.json");
|
|
26546
27036
|
}
|
|
26547
27037
|
function readConfig() {
|
|
26548
27038
|
const configPath = getConfigPath2();
|
|
26549
|
-
if (!
|
|
27039
|
+
if (!existsSync11(configPath))
|
|
26550
27040
|
return {};
|
|
26551
27041
|
try {
|
|
26552
|
-
const raw =
|
|
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 (!
|
|
26561
|
-
|
|
27050
|
+
if (!existsSync11(configDir)) {
|
|
27051
|
+
mkdirSync9(configDir, { recursive: true });
|
|
26562
27052
|
}
|
|
26563
|
-
|
|
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
|
|
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((
|
|
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 &&
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
27749
|
-
|
|
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
|
|
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
|
|
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 :
|
|
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
|
|
28103
|
-
import { join as
|
|
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 =
|
|
28110
|
-
const local =
|
|
28111
|
-
if (
|
|
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 (
|
|
28603
|
+
if (existsSync13(local))
|
|
28114
28604
|
return local;
|
|
28115
28605
|
const home = process.env["HOME"] || "~";
|
|
28116
|
-
return
|
|
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 (!
|
|
28638
|
+
if (!existsSync13(path)) {
|
|
28149
28639
|
cached2 = getDefaultSandboxProfiles();
|
|
28150
28640
|
return cached2;
|
|
28151
28641
|
}
|
|
28152
|
-
const parsed = JSON.parse(
|
|
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
|
-
|
|
28162
|
-
|
|
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
|
|
29011
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
28522
29012
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
28523
|
-
import { resolve as
|
|
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 ?
|
|
28567
|
-
if (!
|
|
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(
|
|
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
|
|
28664
|
-
import { basename as basename3, isAbsolute, join as
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 (!
|
|
31264
|
+
if (!existsSync16(input.file_path))
|
|
30775
31265
|
throw new Error(`File not found: ${input.file_path}`);
|
|
30776
|
-
const raw =
|
|
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
|
|
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 (!
|
|
32101
|
+
if (!existsSync17(path))
|
|
31612
32102
|
throw new Error(`File not found: ${path}`);
|
|
31613
|
-
return JSON.parse(
|
|
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
|
|
31772
|
-
import { join as
|
|
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 ??
|
|
31966
|
-
|
|
31967
|
-
|
|
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 =
|
|
32006
|
-
if (
|
|
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
|
|
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
|
|
32014
|
-
import { join as
|
|
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 =
|
|
32028
|
-
if (!
|
|
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(
|
|
32520
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
32031
32521
|
}
|
|
32032
32522
|
function walkFiles(dir, acc = []) {
|
|
32033
|
-
if (!
|
|
32523
|
+
if (!existsSync19(dir))
|
|
32034
32524
|
return acc;
|
|
32035
32525
|
for (const entry2 of readdirSync3(dir)) {
|
|
32036
|
-
const full =
|
|
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 =
|
|
32055
|
-
if (!
|
|
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 =
|
|
32070
|
-
if (!
|
|
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 =
|
|
32088
|
-
if (!
|
|
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 =
|
|
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
|
|
32395
|
-
import { dirname as dirname11, join as
|
|
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
|
|
32890
|
+
return resolve15(dbPath);
|
|
32401
32891
|
if (process.env["TODOS_DB_PATH"] && process.env["TODOS_DB_PATH"] !== ":memory:") {
|
|
32402
|
-
return
|
|
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 (!
|
|
32902
|
+
if (!existsSync20(source9))
|
|
32413
32903
|
throw new Error(`Database not found: ${source9}`);
|
|
32414
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 ?
|
|
32442
|
-
|
|
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 ?
|
|
32950
|
+
const path = dbPath ? resolve15(dbPath) : resolveDbPath();
|
|
32461
32951
|
const errors = [];
|
|
32462
|
-
if (!
|
|
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 ?
|
|
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 ?
|
|
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(
|
|
33049
|
+
const base = dbPath ? dirname11(resolve15(dbPath)) : dirname11(resolveDbPath());
|
|
32560
33050
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
32561
|
-
return
|
|
33051
|
+
return join16(base, "backups", `todos-${stamp}.db`);
|
|
32562
33052
|
}
|
|
32563
33053
|
function readBackupManifest(backupPath) {
|
|
32564
33054
|
const manifestPath = `${backupPath}.json`;
|
|
32565
|
-
if (!
|
|
33055
|
+
if (!existsSync20(manifestPath))
|
|
32566
33056
|
return null;
|
|
32567
33057
|
try {
|
|
32568
|
-
return JSON.parse(
|
|
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
|
-
|
|
32579
|
-
|
|
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:
|
|
33085
|
-
const { join:
|
|
33086
|
-
|
|
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 =
|
|
33090
|
-
|
|
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 =
|
|
33094
|
-
|
|
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
|
|
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
|
-
|
|
34403
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
35058
|
-
|
|
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
|
|
35662
|
-
import { join as
|
|
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
|
-
|
|
36426
|
+
mkdirSync16(directory, { recursive: true });
|
|
35936
36427
|
const files = [];
|
|
35937
36428
|
for (const entry2 of exportBuiltinTemplateFiles()) {
|
|
35938
|
-
const path =
|
|
35939
|
-
|
|
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
|
|
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
|
-
|
|
36026
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
36249
|
-
if (!
|
|
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 =
|
|
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(
|
|
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
|
|
36357
|
-
return
|
|
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 =
|
|
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 ?
|
|
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(
|
|
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
|
|
36497
|
-
import { dirname as dirname16, join as
|
|
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 ??
|
|
36728
|
-
|
|
36729
|
-
|
|
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 ??
|
|
36876
|
-
|
|
36877
|
-
|
|
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
|
|
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
|
-
|
|
37137
|
-
|
|
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
|
|
37171
|
-
import { join as
|
|
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
|
|
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 (!
|
|
37698
|
+
if (!existsSync22(path))
|
|
37208
37699
|
return emptyStore();
|
|
37209
|
-
const parsed = JSON.parse(
|
|
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
|
-
|
|
37708
|
+
mkdirSync20(join20(path, ".."), { recursive: true });
|
|
37218
37709
|
store.updated_at = new Date().toISOString();
|
|
37219
|
-
|
|
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
|
|
37866
|
-
import { join as
|
|
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
|
|
38361
|
+
return join21(cwd, ".todos", "scaffolds");
|
|
37871
38362
|
}
|
|
37872
38363
|
function storePath(cwd) {
|
|
37873
|
-
return
|
|
38364
|
+
return join21(storeDir(cwd), "store.json");
|
|
37874
38365
|
}
|
|
37875
38366
|
function versionsDir(cwd) {
|
|
37876
|
-
return
|
|
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 (!
|
|
38377
|
+
if (!existsSync23(path))
|
|
37887
38378
|
return emptyStore2();
|
|
37888
|
-
const parsed = JSON.parse(
|
|
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
|
-
|
|
38386
|
+
mkdirSync21(storeDir(cwd), { recursive: true });
|
|
37896
38387
|
store.updated_at = now();
|
|
37897
|
-
|
|
38388
|
+
writeFileSync19(storePath(cwd), JSON.stringify(store, null, 2), "utf8");
|
|
37898
38389
|
}
|
|
37899
38390
|
function snapshotVersion(scaffold, cwd) {
|
|
37900
|
-
|
|
37901
|
-
const path =
|
|
37902
|
-
|
|
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
|
|
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 =
|
|
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
|
|
40490
|
-
import { join as
|
|
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
|
|
40985
|
+
return join23(HOME, ".claude", "tasks", taskListId);
|
|
40495
40986
|
}
|
|
40496
40987
|
function readClaudeTask(dir, filename) {
|
|
40497
|
-
return readJsonFile(
|
|
40988
|
+
return readJsonFile(join23(dir, filename));
|
|
40498
40989
|
}
|
|
40499
40990
|
function writeClaudeTask(dir, task2) {
|
|
40500
|
-
writeJsonFile(
|
|
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 =
|
|
40513
|
-
if (!
|
|
41003
|
+
const path = join23(dir, ".prefix-counter");
|
|
41004
|
+
if (!existsSync24(path))
|
|
40514
41005
|
return 0;
|
|
40515
|
-
const val = parseInt(
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
|
40731
|
-
import { join as
|
|
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"] ||
|
|
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
|
|
41230
|
+
return join24(agentBaseDir(agent), agent, taskListId);
|
|
40740
41231
|
}
|
|
40741
41232
|
function readAgentTask(dir, filename) {
|
|
40742
|
-
return readJsonFile(
|
|
41233
|
+
return readJsonFile(join24(dir, filename));
|
|
40743
41234
|
}
|
|
40744
41235
|
function writeAgentTask(dir, task2) {
|
|
40745
|
-
writeJsonFile(
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
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() ?
|
|
41083
|
-
const gitignorePath =
|
|
41084
|
-
if (!
|
|
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
|
|
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 =
|
|
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 :
|
|
41717
|
+
const fullPath = statSync8(basePath).isFile() ? basePath : join25(basePath, file);
|
|
41227
41718
|
try {
|
|
41228
|
-
const source9 =
|
|
41229
|
-
const relPath = statSync8(basePath).isFile() ? relative6(
|
|
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 =
|
|
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 :
|
|
41748
|
+
const fullPath = statSync8(basePath).isFile() ? basePath : join25(basePath, file);
|
|
41258
41749
|
try {
|
|
41259
|
-
const source9 =
|
|
41260
|
-
const relPath = statSync8(basePath).isFile() ? relative6(
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
42227
|
-
import { basename as basename5, join as
|
|
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(
|
|
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 =
|
|
42508
|
-
if (!
|
|
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() ? [
|
|
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 =
|
|
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 =
|
|
43096
|
+
const root = resolve18(projectPath);
|
|
42606
43097
|
const candidates = [
|
|
42607
|
-
|
|
42608
|
-
|
|
43098
|
+
join26(root, "todos.extension.json"),
|
|
43099
|
+
join26(root, ".todos", "todos.extension.json")
|
|
42609
43100
|
];
|
|
42610
|
-
const extensionDir =
|
|
42611
|
-
if (
|
|
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 =
|
|
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(
|
|
43111
|
+
return candidates.filter(existsSync27);
|
|
42621
43112
|
}
|
|
42622
43113
|
function discoverLocalExtensions(options = {}) {
|
|
42623
43114
|
const config = loadConfig();
|
|
42624
|
-
const projectPath = options.project_path ?
|
|
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("/") ?
|
|
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
|
|
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 (!
|
|
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
|
|
43932
|
-
import { basename as basename6, dirname as dirname18, join as
|
|
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 (!
|
|
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 (!
|
|
44590
|
+
if (!existsSync29(dbPath))
|
|
44100
44591
|
return;
|
|
44101
44592
|
const stamp = now().replace(/[:.]/g, "-");
|
|
44102
|
-
const backupDir =
|
|
44593
|
+
const backupDir = join27(dirname18(dbPath), `${basename6(dbPath)}.backup-${stamp}`);
|
|
44103
44594
|
const files = [];
|
|
44104
|
-
|
|
44595
|
+
mkdirSync22(backupDir, { recursive: true });
|
|
44105
44596
|
for (const source9 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
44106
|
-
if (!
|
|
44597
|
+
if (!existsSync29(source9))
|
|
44107
44598
|
continue;
|
|
44108
|
-
const target =
|
|
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,
|