@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/cli/index.js
CHANGED
|
@@ -1011,7 +1011,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1011
1011
|
this._exitCallback = (err) => {
|
|
1012
1012
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1013
1013
|
throw err;
|
|
1014
|
-
}
|
|
1014
|
+
}
|
|
1015
1015
|
};
|
|
1016
1016
|
}
|
|
1017
1017
|
return this;
|
|
@@ -3282,6 +3282,11 @@ var init_migrations = __esm(() => {
|
|
|
3282
3282
|
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_object ON storage_tombstones(object_type, object_id);
|
|
3283
3283
|
CREATE INDEX IF NOT EXISTS idx_storage_tombstones_updated ON storage_tombstones(updated_at);
|
|
3284
3284
|
INSERT OR IGNORE INTO _migrations (id) VALUES (63);
|
|
3285
|
+
`,
|
|
3286
|
+
`
|
|
3287
|
+
ALTER TABLE plans ADD COLUMN slug TEXT;
|
|
3288
|
+
CREATE INDEX IF NOT EXISTS idx_plans_slug ON plans(slug);
|
|
3289
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (64);
|
|
3285
3290
|
`
|
|
3286
3291
|
];
|
|
3287
3292
|
});
|
|
@@ -3305,6 +3310,29 @@ function runMigrations(db) {
|
|
|
3305
3310
|
}
|
|
3306
3311
|
ensureSchema(db);
|
|
3307
3312
|
}
|
|
3313
|
+
function planSlugBase(value) {
|
|
3314
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "plan";
|
|
3315
|
+
}
|
|
3316
|
+
function backfillPlanSlugs(db) {
|
|
3317
|
+
try {
|
|
3318
|
+
const rows = db.query("SELECT id, project_id, name, slug FROM plans ORDER BY created_at ASC, id ASC").all();
|
|
3319
|
+
const used = new Set;
|
|
3320
|
+
for (const row of rows) {
|
|
3321
|
+
const scope = row.project_id ?? "__global__";
|
|
3322
|
+
const base = planSlugBase(row.slug || row.name);
|
|
3323
|
+
let candidate = base;
|
|
3324
|
+
let suffix = 2;
|
|
3325
|
+
while (used.has(`${scope}:${candidate}`)) {
|
|
3326
|
+
candidate = `${base}-${suffix}`;
|
|
3327
|
+
suffix += 1;
|
|
3328
|
+
}
|
|
3329
|
+
used.add(`${scope}:${candidate}`);
|
|
3330
|
+
if (row.slug !== candidate) {
|
|
3331
|
+
db.run("UPDATE plans SET slug = ? WHERE id = ?", [candidate, row.id]);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
} catch {}
|
|
3335
|
+
}
|
|
3308
3336
|
function ensureSchema(db) {
|
|
3309
3337
|
const ensureColumn = (table, column, type) => {
|
|
3310
3338
|
try {
|
|
@@ -3354,7 +3382,8 @@ function ensureSchema(db) {
|
|
|
3354
3382
|
)`);
|
|
3355
3383
|
ensureTable("plans", `
|
|
3356
3384
|
CREATE TABLE plans (
|
|
3357
|
-
id TEXT PRIMARY KEY,
|
|
3385
|
+
id TEXT PRIMARY KEY, slug TEXT,
|
|
3386
|
+
project_id TEXT REFERENCES projects(id) ON DELETE CASCADE,
|
|
3358
3387
|
task_list_id TEXT, agent_id TEXT,
|
|
3359
3388
|
name TEXT NOT NULL, description TEXT,
|
|
3360
3389
|
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'completed', 'archived')),
|
|
@@ -3869,8 +3898,10 @@ function ensureSchema(db) {
|
|
|
3869
3898
|
ensureColumn("agents", "org_id", "TEXT");
|
|
3870
3899
|
ensureColumn("agents", "capabilities", "TEXT DEFAULT '[]'");
|
|
3871
3900
|
ensureColumn("projects", "org_id", "TEXT");
|
|
3901
|
+
ensureColumn("plans", "slug", "TEXT");
|
|
3872
3902
|
ensureColumn("plans", "task_list_id", "TEXT");
|
|
3873
3903
|
ensureColumn("plans", "agent_id", "TEXT");
|
|
3904
|
+
backfillPlanSlugs(db);
|
|
3874
3905
|
ensureColumn("task_templates", "variables", "TEXT DEFAULT '[]'");
|
|
3875
3906
|
ensureColumn("task_templates", "version", "INTEGER NOT NULL DEFAULT 1");
|
|
3876
3907
|
ensureColumn("template_tasks", "condition", "TEXT");
|
|
@@ -3985,6 +4016,8 @@ function ensureSchema(db) {
|
|
|
3985
4016
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name)");
|
|
3986
4017
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_project ON plans(project_id)");
|
|
3987
4018
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_status ON plans(status)");
|
|
4019
|
+
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_slug ON plans(slug)");
|
|
4020
|
+
ensureIndex("CREATE UNIQUE INDEX IF NOT EXISTS idx_plans_scope_slug ON plans(COALESCE(project_id, ''), slug) WHERE slug IS NOT NULL");
|
|
3988
4021
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_task_list ON plans(task_list_id)");
|
|
3989
4022
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_plans_agent ON plans(agent_id)");
|
|
3990
4023
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_history_task ON task_history(task_id)");
|
|
@@ -4838,6 +4871,9 @@ function clearExpiredLocks(db) {
|
|
|
4838
4871
|
const cutoff = lockExpiryCutoff();
|
|
4839
4872
|
db.run("UPDATE tasks SET locked_by = NULL, locked_at = NULL WHERE locked_at IS NOT NULL AND locked_at < ?", [cutoff]);
|
|
4840
4873
|
}
|
|
4874
|
+
function slugifyRef(value) {
|
|
4875
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4876
|
+
}
|
|
4841
4877
|
function resolvePartialId(db, table, partialId) {
|
|
4842
4878
|
if (!ALLOWED_TABLES.has(table)) {
|
|
4843
4879
|
throw new Error(`Invalid table name: ${table}`);
|
|
@@ -4864,6 +4900,16 @@ function resolvePartialId(db, table, partialId) {
|
|
|
4864
4900
|
if (slugRow)
|
|
4865
4901
|
return slugRow.id;
|
|
4866
4902
|
}
|
|
4903
|
+
if (table === "plans") {
|
|
4904
|
+
const slug = slugifyRef(partialId);
|
|
4905
|
+
if (slug) {
|
|
4906
|
+
const slugRows = db.query("SELECT id FROM plans WHERE slug = ?").all(slug);
|
|
4907
|
+
if (slugRows.length === 1)
|
|
4908
|
+
return slugRows[0].id;
|
|
4909
|
+
if (slugRows.length > 1)
|
|
4910
|
+
return null;
|
|
4911
|
+
}
|
|
4912
|
+
}
|
|
4867
4913
|
if (table === "projects") {
|
|
4868
4914
|
const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
4869
4915
|
if (nameRow)
|
|
@@ -10333,15 +10379,76 @@ var init_task_relations = __esm(() => {
|
|
|
10333
10379
|
});
|
|
10334
10380
|
|
|
10335
10381
|
// src/db/plans.ts
|
|
10382
|
+
function planSlugBase2(value) {
|
|
10383
|
+
return slugify(value) || "plan";
|
|
10384
|
+
}
|
|
10385
|
+
function normalizePlanSlug(value) {
|
|
10386
|
+
const slug = slugify(value);
|
|
10387
|
+
if (!slug)
|
|
10388
|
+
throw new Error("Invalid plan slug");
|
|
10389
|
+
return slug;
|
|
10390
|
+
}
|
|
10391
|
+
function plansBySlug(slug, db, projectId) {
|
|
10392
|
+
if (projectId !== undefined) {
|
|
10393
|
+
if (projectId === null) {
|
|
10394
|
+
return db.query("SELECT * FROM plans WHERE slug = ? AND project_id IS NULL ORDER BY created_at ASC, id ASC").all(slug);
|
|
10395
|
+
}
|
|
10396
|
+
return db.query("SELECT * FROM plans WHERE slug = ? AND project_id = ? ORDER BY created_at ASC, id ASC").all(slug, projectId);
|
|
10397
|
+
}
|
|
10398
|
+
return db.query("SELECT * FROM plans WHERE slug = ? ORDER BY created_at ASC, id ASC").all(slug);
|
|
10399
|
+
}
|
|
10400
|
+
function planSlugExists(slug, projectId, db, excludeId) {
|
|
10401
|
+
const rows = plansBySlug(slug, db, projectId);
|
|
10402
|
+
return rows.some((plan) => plan.id !== excludeId);
|
|
10403
|
+
}
|
|
10404
|
+
function nextPlanSlug(base, projectId, db, excludeId) {
|
|
10405
|
+
let candidate = base;
|
|
10406
|
+
let suffix = 2;
|
|
10407
|
+
while (planSlugExists(candidate, projectId, db, excludeId)) {
|
|
10408
|
+
candidate = `${base}-${suffix}`;
|
|
10409
|
+
suffix += 1;
|
|
10410
|
+
}
|
|
10411
|
+
return candidate;
|
|
10412
|
+
}
|
|
10413
|
+
function resolveCreateSlug(input, projectId, db) {
|
|
10414
|
+
if (input.slug !== undefined) {
|
|
10415
|
+
const slug = normalizePlanSlug(input.slug);
|
|
10416
|
+
if (planSlugExists(slug, projectId, db)) {
|
|
10417
|
+
throw new Error(`Plan slug already exists in this scope: ${slug}`);
|
|
10418
|
+
}
|
|
10419
|
+
return slug;
|
|
10420
|
+
}
|
|
10421
|
+
return nextPlanSlug(planSlugBase2(input.name), projectId, db);
|
|
10422
|
+
}
|
|
10423
|
+
function resolvePlanRefDetailed(ref, db, projectId) {
|
|
10424
|
+
const d = db || getDatabase();
|
|
10425
|
+
const byId = d.query("SELECT * FROM plans WHERE id = ? OR id LIKE ? ORDER BY id").all(ref, `${ref}%`);
|
|
10426
|
+
if (byId.length === 1)
|
|
10427
|
+
return { id: byId[0].id, reason: "id", matches: byId };
|
|
10428
|
+
if (byId.length > 1)
|
|
10429
|
+
return { id: null, reason: "ambiguous", matches: byId };
|
|
10430
|
+
const slug = slugify(ref);
|
|
10431
|
+
if (!slug)
|
|
10432
|
+
return { id: null, reason: "not_found", matches: [] };
|
|
10433
|
+
const bySlug = plansBySlug(slug, d, projectId);
|
|
10434
|
+
if (bySlug.length === 1)
|
|
10435
|
+
return { id: bySlug[0].id, reason: "slug", matches: bySlug };
|
|
10436
|
+
if (bySlug.length > 1)
|
|
10437
|
+
return { id: null, reason: "ambiguous", matches: bySlug };
|
|
10438
|
+
return { id: null, reason: "not_found", matches: [] };
|
|
10439
|
+
}
|
|
10336
10440
|
function createPlan(input, db) {
|
|
10337
10441
|
const d = db || getDatabase();
|
|
10338
10442
|
const id = uuid();
|
|
10339
10443
|
const timestamp = now();
|
|
10444
|
+
const projectId = input.project_id || null;
|
|
10445
|
+
const slug = resolveCreateSlug(input, projectId, d);
|
|
10340
10446
|
const machineId = currentStorageMachineId(d);
|
|
10341
|
-
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
10342
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10447
|
+
d.run(`INSERT INTO plans (id, slug, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at, machine_id)
|
|
10448
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
10343
10449
|
id,
|
|
10344
|
-
|
|
10450
|
+
slug,
|
|
10451
|
+
projectId,
|
|
10345
10452
|
input.task_list_id || null,
|
|
10346
10453
|
input.agent_id || null,
|
|
10347
10454
|
input.name,
|
|
@@ -10376,6 +10483,14 @@ function updatePlan(id, input, db) {
|
|
|
10376
10483
|
sets.push("name = ?");
|
|
10377
10484
|
params.push(input.name);
|
|
10378
10485
|
}
|
|
10486
|
+
if (input.slug !== undefined) {
|
|
10487
|
+
const slug = normalizePlanSlug(input.slug);
|
|
10488
|
+
if (planSlugExists(slug, plan.project_id, d, id)) {
|
|
10489
|
+
throw new Error(`Plan slug already exists in this scope: ${slug}`);
|
|
10490
|
+
}
|
|
10491
|
+
sets.push("slug = ?");
|
|
10492
|
+
params.push(slug);
|
|
10493
|
+
}
|
|
10379
10494
|
if (input.description !== undefined) {
|
|
10380
10495
|
sets.push("description = ?");
|
|
10381
10496
|
params.push(input.description);
|
|
@@ -10420,6 +10535,7 @@ var init_plans = __esm(() => {
|
|
|
10420
10535
|
init_event_emission_safety();
|
|
10421
10536
|
init_event_hooks();
|
|
10422
10537
|
init_database();
|
|
10538
|
+
init_projects();
|
|
10423
10539
|
init_storage_tombstones();
|
|
10424
10540
|
});
|
|
10425
10541
|
|
|
@@ -13681,6 +13797,323 @@ var init_task_commands = __esm(() => {
|
|
|
13681
13797
|
init_types();
|
|
13682
13798
|
});
|
|
13683
13799
|
|
|
13800
|
+
// src/lib/plan-artifacts.ts
|
|
13801
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
13802
|
+
import { join as join7, resolve as resolve10 } from "path";
|
|
13803
|
+
function assertSafePathSegment(value, label) {
|
|
13804
|
+
const trimmed = value.trim();
|
|
13805
|
+
if (!trimmed || trimmed === "." || trimmed === ".." || trimmed.includes("/") || trimmed.includes("\\")) {
|
|
13806
|
+
throw new Error(`Invalid ${label} for plan artifact path`);
|
|
13807
|
+
}
|
|
13808
|
+
if (!/^[A-Za-z0-9._-]+$/.test(trimmed)) {
|
|
13809
|
+
throw new Error(`Invalid ${label} for plan artifact path`);
|
|
13810
|
+
}
|
|
13811
|
+
return trimmed;
|
|
13812
|
+
}
|
|
13813
|
+
function frontmatterScalar(value) {
|
|
13814
|
+
return JSON.stringify(value);
|
|
13815
|
+
}
|
|
13816
|
+
function parseFrontmatterScalar(value) {
|
|
13817
|
+
const trimmed = value.trim();
|
|
13818
|
+
if (trimmed === "null")
|
|
13819
|
+
return null;
|
|
13820
|
+
try {
|
|
13821
|
+
const parsed = JSON.parse(trimmed);
|
|
13822
|
+
if (parsed === null || typeof parsed === "string")
|
|
13823
|
+
return parsed;
|
|
13824
|
+
} catch {}
|
|
13825
|
+
return trimmed.replace(/^["']|["']$/g, "") || null;
|
|
13826
|
+
}
|
|
13827
|
+
function markdownEscape(text) {
|
|
13828
|
+
return text.replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
13829
|
+
}
|
|
13830
|
+
function markdownLine(text) {
|
|
13831
|
+
return markdownEscape(text).replace(/\s+/g, " ").trim();
|
|
13832
|
+
}
|
|
13833
|
+
function projectSlugMatches(project, ref) {
|
|
13834
|
+
const normalized = slugify(ref);
|
|
13835
|
+
return Boolean(normalized) && (project.task_list_id === normalized || slugify(project.name) === normalized);
|
|
13836
|
+
}
|
|
13837
|
+
function planArtifactSlug(plan) {
|
|
13838
|
+
return slugify(plan.slug || plan.name) || "plan";
|
|
13839
|
+
}
|
|
13840
|
+
function resolvePlanArtifactProject(input) {
|
|
13841
|
+
const db = input.db || getDatabase();
|
|
13842
|
+
const ref = input.project_id || input.project_ref;
|
|
13843
|
+
if (!ref)
|
|
13844
|
+
throw new Error("Plan artifacts require a project id or project reference");
|
|
13845
|
+
const byPath = getProjectByPath(resolve10(ref), db);
|
|
13846
|
+
if (byPath)
|
|
13847
|
+
return byPath;
|
|
13848
|
+
const resolvedId = resolvePartialId(db, "projects", ref);
|
|
13849
|
+
if (resolvedId) {
|
|
13850
|
+
const project2 = getProject(resolvedId, db);
|
|
13851
|
+
if (project2)
|
|
13852
|
+
return project2;
|
|
13853
|
+
}
|
|
13854
|
+
const project = listProjects(db).find((candidate) => projectSlugMatches(candidate, ref));
|
|
13855
|
+
if (project)
|
|
13856
|
+
return project;
|
|
13857
|
+
throw new Error(`Project not found for plan artifacts: ${ref}`);
|
|
13858
|
+
}
|
|
13859
|
+
function resolvePlanArtifactPaths(input) {
|
|
13860
|
+
const project = resolvePlanArtifactProject(input);
|
|
13861
|
+
const projectId = assertSafePathSegment(project.id, "project id");
|
|
13862
|
+
const projectRoot = resolve10(project.path);
|
|
13863
|
+
const directory = join7(projectRoot, ".hasna", "todos", "plans", projectId);
|
|
13864
|
+
const planId = input.plan_id ? assertSafePathSegment(input.plan_id, "plan id") : null;
|
|
13865
|
+
const planSlug = input.plan_slug ? assertSafePathSegment(slugify(input.plan_slug), "plan slug") : null;
|
|
13866
|
+
const fileName = planId ? planSlug ? `${planSlug}--${planId.slice(0, 8)}.md` : `${planId}.md` : null;
|
|
13867
|
+
return {
|
|
13868
|
+
project_id: project.id,
|
|
13869
|
+
project_root: projectRoot,
|
|
13870
|
+
directory,
|
|
13871
|
+
file_path: fileName ? join7(directory, fileName) : directory
|
|
13872
|
+
};
|
|
13873
|
+
}
|
|
13874
|
+
function resolvePlanArtifactCandidatePaths(plan, db) {
|
|
13875
|
+
return {
|
|
13876
|
+
primary: resolvePlanArtifactPaths({
|
|
13877
|
+
project_id: plan.project_id,
|
|
13878
|
+
plan_id: plan.id,
|
|
13879
|
+
plan_slug: planArtifactSlug(plan),
|
|
13880
|
+
db
|
|
13881
|
+
}),
|
|
13882
|
+
legacy: resolvePlanArtifactPaths({ project_id: plan.project_id, plan_id: plan.id, db })
|
|
13883
|
+
};
|
|
13884
|
+
}
|
|
13885
|
+
function buildPlanArtifactSnapshot(plan, tasks = [], artifactUpdatedAt = new Date().toISOString()) {
|
|
13886
|
+
if (!plan.project_id)
|
|
13887
|
+
throw new Error("Plan artifacts require a project-scoped plan");
|
|
13888
|
+
const taskReferences = tasks.map((task) => ({
|
|
13889
|
+
task_id: task.id,
|
|
13890
|
+
title: task.title,
|
|
13891
|
+
status: task.status,
|
|
13892
|
+
priority: task.priority
|
|
13893
|
+
}));
|
|
13894
|
+
const body = renderPlanArtifactBody(plan, taskReferences);
|
|
13895
|
+
return {
|
|
13896
|
+
metadata: {
|
|
13897
|
+
schema: PLAN_MARKDOWN_SCHEMA,
|
|
13898
|
+
plan_id: plan.id,
|
|
13899
|
+
plan_slug: plan.slug ?? null,
|
|
13900
|
+
project_id: plan.project_id,
|
|
13901
|
+
task_list_id: plan.task_list_id ?? null,
|
|
13902
|
+
agent_id: plan.agent_id ?? null,
|
|
13903
|
+
stable_id: plan.id,
|
|
13904
|
+
name: plan.name,
|
|
13905
|
+
status: plan.status,
|
|
13906
|
+
created_at: plan.created_at,
|
|
13907
|
+
updated_at: plan.updated_at,
|
|
13908
|
+
artifact_updated_at: artifactUpdatedAt
|
|
13909
|
+
},
|
|
13910
|
+
task_references: taskReferences,
|
|
13911
|
+
body
|
|
13912
|
+
};
|
|
13913
|
+
}
|
|
13914
|
+
function renderPlanArtifactBody(plan, tasks) {
|
|
13915
|
+
const lines = [`# ${markdownLine(plan.name) || plan.id}`, ""];
|
|
13916
|
+
if (plan.description?.trim()) {
|
|
13917
|
+
lines.push(markdownEscape(plan.description), "");
|
|
13918
|
+
}
|
|
13919
|
+
lines.push("## Tasks", "");
|
|
13920
|
+
if (tasks.length === 0) {
|
|
13921
|
+
lines.push("_No tasks are currently attached to this plan._", "");
|
|
13922
|
+
} else {
|
|
13923
|
+
for (const task of tasks) {
|
|
13924
|
+
const check = task.status === "completed" ? "x" : " ";
|
|
13925
|
+
lines.push(`- [${check}] ${markdownLine(task.title) || task.task_id}`);
|
|
13926
|
+
lines.push(` <!-- todos: task_id=${task.task_id} status=${task.status} priority=${task.priority} -->`);
|
|
13927
|
+
}
|
|
13928
|
+
lines.push("");
|
|
13929
|
+
}
|
|
13930
|
+
return lines.join(`
|
|
13931
|
+
`);
|
|
13932
|
+
}
|
|
13933
|
+
function renderPlanArtifactMarkdown(snapshot) {
|
|
13934
|
+
const metadata = snapshot.metadata;
|
|
13935
|
+
const lines = [
|
|
13936
|
+
"---",
|
|
13937
|
+
`schema: ${frontmatterScalar(metadata.schema)}`,
|
|
13938
|
+
`plan_id: ${frontmatterScalar(metadata.plan_id)}`,
|
|
13939
|
+
`plan_slug: ${frontmatterScalar(metadata.plan_slug)}`,
|
|
13940
|
+
`project_id: ${frontmatterScalar(metadata.project_id)}`,
|
|
13941
|
+
`task_list_id: ${frontmatterScalar(metadata.task_list_id)}`,
|
|
13942
|
+
`agent_id: ${frontmatterScalar(metadata.agent_id)}`,
|
|
13943
|
+
`stable_id: ${frontmatterScalar(metadata.stable_id)}`,
|
|
13944
|
+
`name: ${frontmatterScalar(metadata.name)}`,
|
|
13945
|
+
`status: ${frontmatterScalar(metadata.status)}`,
|
|
13946
|
+
`created_at: ${frontmatterScalar(metadata.created_at)}`,
|
|
13947
|
+
`updated_at: ${frontmatterScalar(metadata.updated_at)}`,
|
|
13948
|
+
`artifact_updated_at: ${frontmatterScalar(metadata.artifact_updated_at)}`,
|
|
13949
|
+
"---",
|
|
13950
|
+
"",
|
|
13951
|
+
snapshot.body
|
|
13952
|
+
];
|
|
13953
|
+
return `${lines.join(`
|
|
13954
|
+
`).replace(/\n{3,}/g, `
|
|
13955
|
+
|
|
13956
|
+
`).trimEnd()}
|
|
13957
|
+
`;
|
|
13958
|
+
}
|
|
13959
|
+
function parsePlanArtifactMarkdown(markdown) {
|
|
13960
|
+
const match = markdown.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
13961
|
+
if (!match)
|
|
13962
|
+
throw new Error("Invalid plan artifact: missing frontmatter");
|
|
13963
|
+
const rawMetadata = {};
|
|
13964
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
13965
|
+
const separator = line.indexOf(":");
|
|
13966
|
+
if (separator === -1)
|
|
13967
|
+
continue;
|
|
13968
|
+
const key = line.slice(0, separator).trim();
|
|
13969
|
+
const value = line.slice(separator + 1);
|
|
13970
|
+
rawMetadata[key] = parseFrontmatterScalar(value);
|
|
13971
|
+
}
|
|
13972
|
+
if (rawMetadata.schema !== PLAN_MARKDOWN_SCHEMA) {
|
|
13973
|
+
throw new Error(`Unsupported plan artifact schema: ${rawMetadata.schema ?? "unknown"}`);
|
|
13974
|
+
}
|
|
13975
|
+
const required = ["plan_id", "project_id", "stable_id", "name", "status", "created_at", "updated_at", "artifact_updated_at"];
|
|
13976
|
+
for (const key of required) {
|
|
13977
|
+
if (!rawMetadata[key])
|
|
13978
|
+
throw new Error(`Invalid plan artifact: missing ${key}`);
|
|
13979
|
+
}
|
|
13980
|
+
const body = match[2] ?? "";
|
|
13981
|
+
return {
|
|
13982
|
+
metadata: {
|
|
13983
|
+
schema: PLAN_MARKDOWN_SCHEMA,
|
|
13984
|
+
plan_id: rawMetadata.plan_id,
|
|
13985
|
+
plan_slug: rawMetadata.plan_slug ?? null,
|
|
13986
|
+
project_id: rawMetadata.project_id,
|
|
13987
|
+
task_list_id: rawMetadata.task_list_id ?? null,
|
|
13988
|
+
agent_id: rawMetadata.agent_id ?? null,
|
|
13989
|
+
stable_id: rawMetadata.stable_id,
|
|
13990
|
+
name: rawMetadata.name,
|
|
13991
|
+
status: rawMetadata.status,
|
|
13992
|
+
created_at: rawMetadata.created_at,
|
|
13993
|
+
updated_at: rawMetadata.updated_at,
|
|
13994
|
+
artifact_updated_at: rawMetadata.artifact_updated_at
|
|
13995
|
+
},
|
|
13996
|
+
task_references: parseTaskReferences(body),
|
|
13997
|
+
body
|
|
13998
|
+
};
|
|
13999
|
+
}
|
|
14000
|
+
function parseTaskReferences(body) {
|
|
14001
|
+
const references = [];
|
|
14002
|
+
const taskLine = /^\s*-\s+\[[ xX]\]\s+(.+)$/;
|
|
14003
|
+
const metadataLine = /<!--\s*todos:\s*task_id=([A-Za-z0-9._-]+)\s+status=([A-Za-z_]+)\s+priority=([A-Za-z_]+)\s*-->/;
|
|
14004
|
+
const lines = body.split(/\r?\n/);
|
|
14005
|
+
for (let index = 0;index < lines.length; index++) {
|
|
14006
|
+
const titleMatch = lines[index].match(taskLine);
|
|
14007
|
+
if (!titleMatch)
|
|
14008
|
+
continue;
|
|
14009
|
+
const metadataMatch = lines[index + 1]?.match(metadataLine);
|
|
14010
|
+
if (!metadataMatch)
|
|
14011
|
+
continue;
|
|
14012
|
+
references.push({
|
|
14013
|
+
task_id: metadataMatch[1],
|
|
14014
|
+
title: titleMatch[1].trim(),
|
|
14015
|
+
status: metadataMatch[2],
|
|
14016
|
+
priority: metadataMatch[3]
|
|
14017
|
+
});
|
|
14018
|
+
}
|
|
14019
|
+
return references;
|
|
14020
|
+
}
|
|
14021
|
+
function writePlanArtifact(plan, db) {
|
|
14022
|
+
if (!plan.project_id)
|
|
14023
|
+
return null;
|
|
14024
|
+
const d = db || getDatabase();
|
|
14025
|
+
const tasks = listTasks({ plan_id: plan.id, include_archived: true }, d);
|
|
14026
|
+
const paths = resolvePlanArtifactCandidatePaths(plan, d).primary;
|
|
14027
|
+
const snapshot = buildPlanArtifactSnapshot(plan, tasks);
|
|
14028
|
+
mkdirSync5(paths.directory, { recursive: true });
|
|
14029
|
+
writeFileSync3(paths.file_path, renderPlanArtifactMarkdown(snapshot), "utf8");
|
|
14030
|
+
return { path: paths.file_path, snapshot };
|
|
14031
|
+
}
|
|
14032
|
+
function readPlanArtifact(plan, db) {
|
|
14033
|
+
if (!plan.project_id)
|
|
14034
|
+
return null;
|
|
14035
|
+
const d = db || getDatabase();
|
|
14036
|
+
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
14037
|
+
const path = existsSync8(paths.primary.file_path) ? paths.primary.file_path : existsSync8(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
14038
|
+
if (!path)
|
|
14039
|
+
return null;
|
|
14040
|
+
const markdown = readFileSync4(path, "utf8");
|
|
14041
|
+
return {
|
|
14042
|
+
path,
|
|
14043
|
+
markdown,
|
|
14044
|
+
...parsePlanArtifactMarkdown(markdown)
|
|
14045
|
+
};
|
|
14046
|
+
}
|
|
14047
|
+
function inspectPlanArtifact(plan, db) {
|
|
14048
|
+
if (!plan.project_id)
|
|
14049
|
+
return null;
|
|
14050
|
+
const d = db || getDatabase();
|
|
14051
|
+
const paths = resolvePlanArtifactCandidatePaths(plan, d);
|
|
14052
|
+
const path = existsSync8(paths.primary.file_path) ? paths.primary.file_path : existsSync8(paths.legacy.file_path) ? paths.legacy.file_path : null;
|
|
14053
|
+
if (!path) {
|
|
14054
|
+
return {
|
|
14055
|
+
path: paths.primary.file_path,
|
|
14056
|
+
exists: false,
|
|
14057
|
+
parse_error: null,
|
|
14058
|
+
metadata: null,
|
|
14059
|
+
task_references: [],
|
|
14060
|
+
conflicts: []
|
|
14061
|
+
};
|
|
14062
|
+
}
|
|
14063
|
+
try {
|
|
14064
|
+
const artifact = parsePlanArtifactMarkdown(readFileSync4(path, "utf8"));
|
|
14065
|
+
return {
|
|
14066
|
+
path,
|
|
14067
|
+
exists: true,
|
|
14068
|
+
parse_error: null,
|
|
14069
|
+
metadata: artifact.metadata,
|
|
14070
|
+
task_references: artifact.task_references,
|
|
14071
|
+
conflicts: comparePlanArtifact(plan, artifact, listTasks({ plan_id: plan.id, include_archived: true }, d))
|
|
14072
|
+
};
|
|
14073
|
+
} catch (error) {
|
|
14074
|
+
return {
|
|
14075
|
+
path,
|
|
14076
|
+
exists: true,
|
|
14077
|
+
parse_error: error instanceof Error ? error.message : String(error),
|
|
14078
|
+
metadata: null,
|
|
14079
|
+
task_references: [],
|
|
14080
|
+
conflicts: []
|
|
14081
|
+
};
|
|
14082
|
+
}
|
|
14083
|
+
}
|
|
14084
|
+
function comparePlanArtifact(plan, artifact, tasks) {
|
|
14085
|
+
const conflicts = [];
|
|
14086
|
+
compare("plan_id", plan.id, artifact.metadata.plan_id, conflicts);
|
|
14087
|
+
if (artifact.metadata.plan_slug !== null) {
|
|
14088
|
+
compare("plan_slug", plan.slug ?? null, artifact.metadata.plan_slug, conflicts);
|
|
14089
|
+
}
|
|
14090
|
+
compare("project_id", plan.project_id ?? null, artifact.metadata.project_id, conflicts);
|
|
14091
|
+
compare("name", plan.name, artifact.metadata.name, conflicts);
|
|
14092
|
+
compare("status", plan.status, artifact.metadata.status, conflicts);
|
|
14093
|
+
compare("updated_at", plan.updated_at, artifact.metadata.updated_at, conflicts);
|
|
14094
|
+
const dbTaskIds = tasks.map((task) => task.id).sort();
|
|
14095
|
+
const artifactTaskIds = artifact.task_references.map((task) => task.task_id).sort();
|
|
14096
|
+
if (dbTaskIds.join(",") !== artifactTaskIds.join(",")) {
|
|
14097
|
+
conflicts.push({
|
|
14098
|
+
field: "task_references",
|
|
14099
|
+
database: dbTaskIds.join(",") || null,
|
|
14100
|
+
artifact: artifactTaskIds.join(",") || null
|
|
14101
|
+
});
|
|
14102
|
+
}
|
|
14103
|
+
return conflicts;
|
|
14104
|
+
}
|
|
14105
|
+
function compare(field, database, artifact, conflicts) {
|
|
14106
|
+
if ((database ?? null) !== (artifact ?? null)) {
|
|
14107
|
+
conflicts.push({ field, database: database ?? null, artifact: artifact ?? null });
|
|
14108
|
+
}
|
|
14109
|
+
}
|
|
14110
|
+
var PLAN_MARKDOWN_SCHEMA = "hasna.todos.plan/v1";
|
|
14111
|
+
var init_plan_artifacts = __esm(() => {
|
|
14112
|
+
init_database();
|
|
14113
|
+
init_projects();
|
|
14114
|
+
init_tasks();
|
|
14115
|
+
});
|
|
14116
|
+
|
|
13684
14117
|
// src/db/builtin-templates.ts
|
|
13685
14118
|
var exports_builtin_templates = {};
|
|
13686
14119
|
__export(exports_builtin_templates, {
|
|
@@ -13695,8 +14128,8 @@ __export(exports_builtin_templates, {
|
|
|
13695
14128
|
BUILTIN_TEMPLATE_LIBRARY_SOURCE: () => BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
13696
14129
|
BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
|
|
13697
14130
|
});
|
|
13698
|
-
import { mkdirSync as
|
|
13699
|
-
import { join as
|
|
14131
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
14132
|
+
import { join as join8 } from "path";
|
|
13700
14133
|
function templateMetadata(template) {
|
|
13701
14134
|
return {
|
|
13702
14135
|
source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
|
|
@@ -13752,11 +14185,11 @@ function exportBuiltinTemplateFiles() {
|
|
|
13752
14185
|
}));
|
|
13753
14186
|
}
|
|
13754
14187
|
function writeBuiltinTemplateFiles(directory) {
|
|
13755
|
-
|
|
14188
|
+
mkdirSync6(directory, { recursive: true });
|
|
13756
14189
|
const files = [];
|
|
13757
14190
|
for (const entry of exportBuiltinTemplateFiles()) {
|
|
13758
|
-
const path =
|
|
13759
|
-
|
|
14191
|
+
const path = join8(directory, entry.filename);
|
|
14192
|
+
writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
|
|
13760
14193
|
`, "utf-8");
|
|
13761
14194
|
files.push(path);
|
|
13762
14195
|
}
|
|
@@ -14023,31 +14456,105 @@ __export(exports_plan_template_commands, {
|
|
|
14023
14456
|
registerPlanTemplateCommands: () => registerPlanTemplateCommands
|
|
14024
14457
|
});
|
|
14025
14458
|
import chalk3 from "chalk";
|
|
14459
|
+
function resolvePlanCliRef(ref, projectId) {
|
|
14460
|
+
const db = getDatabase();
|
|
14461
|
+
const resolved = resolvePlanRefDetailed(ref, db, projectId);
|
|
14462
|
+
if (resolved.id)
|
|
14463
|
+
return resolved.id;
|
|
14464
|
+
if (resolved.reason === "ambiguous") {
|
|
14465
|
+
console.error(chalk3.red(`Ambiguous plan reference: ${ref}`));
|
|
14466
|
+
if (resolved.matches.length > 0) {
|
|
14467
|
+
console.error(chalk3.dim(`Matches: ${resolved.matches.map((plan) => `${plan.slug ?? plan.name} (${plan.id.slice(0, 8)})`).join(", ")}`));
|
|
14468
|
+
}
|
|
14469
|
+
} else {
|
|
14470
|
+
console.error(chalk3.red(`Could not resolve plan ID or slug: ${ref}`));
|
|
14471
|
+
}
|
|
14472
|
+
process.exit(1);
|
|
14473
|
+
}
|
|
14026
14474
|
function registerPlanTemplateCommands(program2) {
|
|
14027
|
-
program2.command("plans").description("List and manage plans").option("--add <name>", "Create a plan").option("-d, --description <text>", "Plan description (with --add)").option("--show <id>", "Show plan details with its tasks").option("--delete <id>", "Delete a plan").option("--complete <id>", "Mark a plan as completed").action((opts) => {
|
|
14475
|
+
program2.command("plans").description("List and manage plans").option("--add <name>", "Create a plan").option("--slug <slug>", "Readable plan slug (with --add)").option("-d, --description <text>", "Plan description (with --add)").option("--show <id-or-slug>", "Show plan details with its tasks").option("--artifact <id-or-slug>", "Show local Markdown artifact diagnostics for a plan").option("--write-artifacts", "Write local Markdown artifacts for all project-scoped plans in scope").option("--delete <id>", "Delete a plan").option("--complete <id>", "Mark a plan as completed").action((opts) => {
|
|
14028
14476
|
const globalOpts = program2.opts();
|
|
14029
14477
|
const projectId = autoProject(globalOpts);
|
|
14030
14478
|
if (opts.add) {
|
|
14031
|
-
|
|
14032
|
-
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
|
|
14479
|
+
let plan;
|
|
14480
|
+
try {
|
|
14481
|
+
plan = createPlan({
|
|
14482
|
+
name: opts.add,
|
|
14483
|
+
slug: opts.slug,
|
|
14484
|
+
description: opts.description,
|
|
14485
|
+
project_id: projectId
|
|
14486
|
+
});
|
|
14487
|
+
} catch (error) {
|
|
14488
|
+
handleError(error);
|
|
14489
|
+
}
|
|
14490
|
+
const artifact = writePlanArtifact(plan);
|
|
14036
14491
|
if (globalOpts.json) {
|
|
14037
14492
|
output(plan, true);
|
|
14038
14493
|
} else {
|
|
14039
14494
|
console.log(chalk3.green("Plan created:"));
|
|
14040
14495
|
console.log(`${chalk3.dim(plan.id.slice(0, 8))} ${chalk3.bold(plan.name)} ${chalk3.cyan(`[${plan.status}]`)}`);
|
|
14496
|
+
console.log(`${chalk3.dim("Slug:")} ${plan.slug}`);
|
|
14497
|
+
if (artifact)
|
|
14498
|
+
console.log(`${chalk3.dim("Artifact:")} ${artifact.path}`);
|
|
14041
14499
|
}
|
|
14042
14500
|
return;
|
|
14043
14501
|
}
|
|
14044
|
-
if (opts.
|
|
14502
|
+
if (opts.artifact) {
|
|
14045
14503
|
const db = getDatabase();
|
|
14046
|
-
const resolvedId =
|
|
14047
|
-
|
|
14048
|
-
|
|
14504
|
+
const resolvedId = resolvePlanCliRef(opts.artifact, projectId);
|
|
14505
|
+
const plan = getPlan(resolvedId);
|
|
14506
|
+
if (!plan) {
|
|
14507
|
+
console.error(chalk3.red(`Plan not found: ${opts.artifact}`));
|
|
14049
14508
|
process.exit(1);
|
|
14050
14509
|
}
|
|
14510
|
+
const inspection = inspectPlanArtifact(plan, db);
|
|
14511
|
+
if (!inspection) {
|
|
14512
|
+
const result = { plan_id: plan.id, artifact: null, reason: "plan is not project-scoped" };
|
|
14513
|
+
if (globalOpts.json)
|
|
14514
|
+
output(result, true);
|
|
14515
|
+
else
|
|
14516
|
+
console.log(chalk3.dim("Plan is not project-scoped; no local Markdown artifact path is available."));
|
|
14517
|
+
return;
|
|
14518
|
+
}
|
|
14519
|
+
if (globalOpts.json) {
|
|
14520
|
+
output({ plan, artifact: inspection }, true);
|
|
14521
|
+
return;
|
|
14522
|
+
}
|
|
14523
|
+
console.log(chalk3.bold(`Plan Artifact:
|
|
14524
|
+
`));
|
|
14525
|
+
console.log(` ${chalk3.dim("Plan:")} ${plan.id}`);
|
|
14526
|
+
console.log(` ${chalk3.dim("Path:")} ${inspection.path}`);
|
|
14527
|
+
console.log(` ${chalk3.dim("Exists:")} ${inspection.exists ? "yes" : "no"}`);
|
|
14528
|
+
if (inspection.parse_error)
|
|
14529
|
+
console.log(` ${chalk3.dim("Parse:")} ${chalk3.red(inspection.parse_error)}`);
|
|
14530
|
+
console.log(` ${chalk3.dim("Conflicts:")} ${inspection.conflicts.length}`);
|
|
14531
|
+
for (const conflict of inspection.conflicts) {
|
|
14532
|
+
console.log(` ${conflict.field}: db=${conflict.database ?? "null"} artifact=${conflict.artifact ?? "null"}`);
|
|
14533
|
+
}
|
|
14534
|
+
return;
|
|
14535
|
+
}
|
|
14536
|
+
if (opts.writeArtifacts) {
|
|
14537
|
+
const plans2 = listPlans(projectId);
|
|
14538
|
+
const written = plans2.map((plan) => ({ plan, artifact: writePlanArtifact(plan) })).filter((entry) => entry.artifact);
|
|
14539
|
+
const result = {
|
|
14540
|
+
count: written.length,
|
|
14541
|
+
artifacts: written.map((entry) => ({
|
|
14542
|
+
plan_id: entry.plan.id,
|
|
14543
|
+
path: entry.artifact.path
|
|
14544
|
+
}))
|
|
14545
|
+
};
|
|
14546
|
+
if (globalOpts.json) {
|
|
14547
|
+
output(result, true);
|
|
14548
|
+
} else {
|
|
14549
|
+
console.log(chalk3.green(`Wrote ${written.length} plan artifact(s).`));
|
|
14550
|
+
for (const artifact of result.artifacts)
|
|
14551
|
+
console.log(`${chalk3.dim(artifact.plan_id.slice(0, 8))} ${artifact.path}`);
|
|
14552
|
+
}
|
|
14553
|
+
return;
|
|
14554
|
+
}
|
|
14555
|
+
if (opts.show) {
|
|
14556
|
+
const db = getDatabase();
|
|
14557
|
+
const resolvedId = resolvePlanCliRef(opts.show, projectId);
|
|
14051
14558
|
const plan = getPlan(resolvedId);
|
|
14052
14559
|
if (!plan) {
|
|
14053
14560
|
console.error(chalk3.red(`Plan not found: ${opts.show}`));
|
|
@@ -14055,19 +14562,33 @@ function registerPlanTemplateCommands(program2) {
|
|
|
14055
14562
|
}
|
|
14056
14563
|
const { listTasks: listTasks2 } = (init_tasks(), __toCommonJS(exports_tasks));
|
|
14057
14564
|
const tasks = listTasks2({ plan_id: resolvedId });
|
|
14565
|
+
const artifact = readPlanArtifact(plan, db);
|
|
14058
14566
|
if (globalOpts.json) {
|
|
14059
|
-
output({
|
|
14567
|
+
output({
|
|
14568
|
+
plan,
|
|
14569
|
+
tasks,
|
|
14570
|
+
artifact: artifact ? {
|
|
14571
|
+
path: artifact.path,
|
|
14572
|
+
metadata: artifact.metadata,
|
|
14573
|
+
task_references: artifact.task_references,
|
|
14574
|
+
body: artifact.body
|
|
14575
|
+
} : null
|
|
14576
|
+
}, true);
|
|
14060
14577
|
return;
|
|
14061
14578
|
}
|
|
14062
14579
|
console.log(chalk3.bold(`Plan Details:
|
|
14063
14580
|
`));
|
|
14064
14581
|
console.log(` ${chalk3.dim("ID:")} ${plan.id}`);
|
|
14582
|
+
if (plan.slug)
|
|
14583
|
+
console.log(` ${chalk3.dim("Slug:")} ${plan.slug}`);
|
|
14065
14584
|
console.log(` ${chalk3.dim("Name:")} ${plan.name}`);
|
|
14066
14585
|
console.log(` ${chalk3.dim("Status:")} ${chalk3.cyan(plan.status)}`);
|
|
14067
14586
|
if (plan.description)
|
|
14068
14587
|
console.log(` ${chalk3.dim("Desc:")} ${plan.description}`);
|
|
14069
14588
|
if (plan.project_id)
|
|
14070
14589
|
console.log(` ${chalk3.dim("Project:")} ${plan.project_id}`);
|
|
14590
|
+
if (artifact)
|
|
14591
|
+
console.log(` ${chalk3.dim("Artifact:")} ${artifact.path}`);
|
|
14071
14592
|
console.log(` ${chalk3.dim("Created:")} ${plan.created_at}`);
|
|
14072
14593
|
if (tasks.length > 0) {
|
|
14073
14594
|
console.log(chalk3.bold(`
|
|
@@ -14082,12 +14603,7 @@ function registerPlanTemplateCommands(program2) {
|
|
|
14082
14603
|
return;
|
|
14083
14604
|
}
|
|
14084
14605
|
if (opts.delete) {
|
|
14085
|
-
const
|
|
14086
|
-
const resolvedId = resolvePartialId(db, "plans", opts.delete);
|
|
14087
|
-
if (!resolvedId) {
|
|
14088
|
-
console.error(chalk3.red(`Could not resolve plan ID: ${opts.delete}`));
|
|
14089
|
-
process.exit(1);
|
|
14090
|
-
}
|
|
14606
|
+
const resolvedId = resolvePlanCliRef(opts.delete, projectId);
|
|
14091
14607
|
const deleted = deletePlan(resolvedId);
|
|
14092
14608
|
if (globalOpts.json) {
|
|
14093
14609
|
output({ deleted }, true);
|
|
@@ -14100,19 +14616,17 @@ function registerPlanTemplateCommands(program2) {
|
|
|
14100
14616
|
return;
|
|
14101
14617
|
}
|
|
14102
14618
|
if (opts.complete) {
|
|
14103
|
-
const
|
|
14104
|
-
const resolvedId = resolvePartialId(db, "plans", opts.complete);
|
|
14105
|
-
if (!resolvedId) {
|
|
14106
|
-
console.error(chalk3.red(`Could not resolve plan ID: ${opts.complete}`));
|
|
14107
|
-
process.exit(1);
|
|
14108
|
-
}
|
|
14619
|
+
const resolvedId = resolvePlanCliRef(opts.complete, projectId);
|
|
14109
14620
|
try {
|
|
14110
14621
|
const plan = updatePlan(resolvedId, { status: "completed" });
|
|
14622
|
+
const artifact = writePlanArtifact(plan);
|
|
14111
14623
|
if (globalOpts.json) {
|
|
14112
14624
|
output(plan, true);
|
|
14113
14625
|
} else {
|
|
14114
14626
|
console.log(chalk3.green("Plan completed:"));
|
|
14115
14627
|
console.log(`${chalk3.dim(plan.id.slice(0, 8))} ${chalk3.bold(plan.name)} ${chalk3.cyan(`[${plan.status}]`)}`);
|
|
14628
|
+
if (artifact)
|
|
14629
|
+
console.log(`${chalk3.dim("Artifact:")} ${artifact.path}`);
|
|
14116
14630
|
}
|
|
14117
14631
|
} catch (e) {
|
|
14118
14632
|
handleError(e);
|
|
@@ -14132,7 +14646,8 @@ function registerPlanTemplateCommands(program2) {
|
|
|
14132
14646
|
`));
|
|
14133
14647
|
for (const p of plans) {
|
|
14134
14648
|
const desc = p.description ? chalk3.dim(` - ${p.description}`) : "";
|
|
14135
|
-
|
|
14649
|
+
const slug = p.slug ? chalk3.dim(` ${p.slug}`) : "";
|
|
14650
|
+
console.log(`${chalk3.dim(p.id.slice(0, 8))}${slug} ${chalk3.bold(p.name)} ${chalk3.cyan(`[${p.status}]`)}${desc}`);
|
|
14136
14651
|
}
|
|
14137
14652
|
});
|
|
14138
14653
|
program2.command("templates").description("List and manage task templates").option("--add <name>", "Create a template").option("--title <pattern>", "Title pattern (with --add)").option("-d, --description <text>", "Default description").option("-p, --priority <level>", "Default priority").option("-t, --tags <tags>", "Default tags (comma-separated)").option("--delete <id>", "Delete a template").option("--update <id>", "Update a template").option("--use <id>", "Create a task from a template").option("--var <vars...>", "Variable substitutions: key=value (e.g. --var feature=login)").action(async (opts) => {
|
|
@@ -14390,14 +14905,14 @@ function registerPlanTemplateCommands(program2) {
|
|
|
14390
14905
|
program2.command("template-import [file]").alias("templates-import").description("Import a template from a JSON file").option("--file <path>", "Path to template JSON file (alternative to positional arg)").action(async (file, opts) => {
|
|
14391
14906
|
const globalOpts = program2.opts();
|
|
14392
14907
|
const { importTemplate: importTemplate2 } = await Promise.resolve().then(() => (init_templates(), exports_templates));
|
|
14393
|
-
const { readFileSync:
|
|
14908
|
+
const { readFileSync: readFileSync5 } = await import("fs");
|
|
14394
14909
|
try {
|
|
14395
14910
|
const filePath = file || opts.file;
|
|
14396
14911
|
if (!filePath) {
|
|
14397
14912
|
console.error(chalk3.red("Provide a file path: todos template-import <file> or --file <path>"));
|
|
14398
14913
|
process.exit(1);
|
|
14399
14914
|
}
|
|
14400
|
-
const content =
|
|
14915
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
14401
14916
|
const json = JSON.parse(content);
|
|
14402
14917
|
const template = importTemplate2(json);
|
|
14403
14918
|
if (globalOpts.json) {
|
|
@@ -14441,6 +14956,7 @@ var init_plan_template_commands = __esm(() => {
|
|
|
14441
14956
|
init_database();
|
|
14442
14957
|
init_plans();
|
|
14443
14958
|
init_tasks();
|
|
14959
|
+
init_plan_artifacts();
|
|
14444
14960
|
init_helpers();
|
|
14445
14961
|
});
|
|
14446
14962
|
|
|
@@ -15009,16 +15525,16 @@ var init_saved_search_views = __esm(() => {
|
|
|
15009
15525
|
});
|
|
15010
15526
|
|
|
15011
15527
|
// src/lib/claude-tasks.ts
|
|
15012
|
-
import { existsSync as
|
|
15013
|
-
import { join as
|
|
15528
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
15529
|
+
import { join as join9 } from "path";
|
|
15014
15530
|
function getTaskListDir(taskListId) {
|
|
15015
|
-
return
|
|
15531
|
+
return join9(HOME, ".claude", "tasks", taskListId);
|
|
15016
15532
|
}
|
|
15017
15533
|
function readClaudeTask(dir, filename) {
|
|
15018
|
-
return readJsonFile(
|
|
15534
|
+
return readJsonFile(join9(dir, filename));
|
|
15019
15535
|
}
|
|
15020
15536
|
function writeClaudeTask(dir, task) {
|
|
15021
|
-
writeJsonFile(
|
|
15537
|
+
writeJsonFile(join9(dir, `${task.id}.json`), task);
|
|
15022
15538
|
}
|
|
15023
15539
|
function toClaudeStatus(status) {
|
|
15024
15540
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -15030,14 +15546,14 @@ function toSqliteStatus(status) {
|
|
|
15030
15546
|
return status;
|
|
15031
15547
|
}
|
|
15032
15548
|
function readPrefixCounter(dir) {
|
|
15033
|
-
const path =
|
|
15034
|
-
if (!
|
|
15549
|
+
const path = join9(dir, ".prefix-counter");
|
|
15550
|
+
if (!existsSync9(path))
|
|
15035
15551
|
return 0;
|
|
15036
|
-
const val = parseInt(
|
|
15552
|
+
const val = parseInt(readFileSync5(path, "utf-8").trim(), 10);
|
|
15037
15553
|
return isNaN(val) ? 0 : val;
|
|
15038
15554
|
}
|
|
15039
15555
|
function writePrefixCounter(dir, value) {
|
|
15040
|
-
|
|
15556
|
+
writeFileSync5(join9(dir, ".prefix-counter"), String(value));
|
|
15041
15557
|
}
|
|
15042
15558
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
15043
15559
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -15064,7 +15580,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
15064
15580
|
}
|
|
15065
15581
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
15066
15582
|
const dir = getTaskListDir(taskListId);
|
|
15067
|
-
if (!
|
|
15583
|
+
if (!existsSync9(dir))
|
|
15068
15584
|
ensureDir2(dir);
|
|
15069
15585
|
const filter = {};
|
|
15070
15586
|
if (projectId)
|
|
@@ -15073,7 +15589,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
15073
15589
|
const existingByTodosId = new Map;
|
|
15074
15590
|
const files = listJsonFiles(dir);
|
|
15075
15591
|
for (const f of files) {
|
|
15076
|
-
const path =
|
|
15592
|
+
const path = join9(dir, f);
|
|
15077
15593
|
const ct = readClaudeTask(dir, f);
|
|
15078
15594
|
if (ct?.metadata?.["todos_id"]) {
|
|
15079
15595
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -15160,7 +15676,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
15160
15676
|
}
|
|
15161
15677
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
15162
15678
|
const dir = getTaskListDir(taskListId);
|
|
15163
|
-
if (!
|
|
15679
|
+
if (!existsSync9(dir)) {
|
|
15164
15680
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
15165
15681
|
}
|
|
15166
15682
|
const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -15180,7 +15696,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
15180
15696
|
}
|
|
15181
15697
|
for (const f of files) {
|
|
15182
15698
|
try {
|
|
15183
|
-
const filePath =
|
|
15699
|
+
const filePath = join9(dir, f);
|
|
15184
15700
|
const ct = readClaudeTask(dir, f);
|
|
15185
15701
|
if (!ct)
|
|
15186
15702
|
continue;
|
|
@@ -15253,20 +15769,20 @@ var init_claude_tasks = __esm(() => {
|
|
|
15253
15769
|
});
|
|
15254
15770
|
|
|
15255
15771
|
// src/lib/agent-tasks.ts
|
|
15256
|
-
import { existsSync as
|
|
15257
|
-
import { join as
|
|
15772
|
+
import { existsSync as existsSync10 } from "fs";
|
|
15773
|
+
import { join as join10 } from "path";
|
|
15258
15774
|
function agentBaseDir(agent) {
|
|
15259
15775
|
const key = `TODOS_${agent.toUpperCase()}_TASKS_DIR`;
|
|
15260
|
-
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] ||
|
|
15776
|
+
return process.env[key] || getAgentTasksDir(agent) || process.env["TODOS_AGENT_TASKS_DIR"] || join10(getTodosGlobalDir(), "agents");
|
|
15261
15777
|
}
|
|
15262
15778
|
function getTaskListDir2(agent, taskListId) {
|
|
15263
|
-
return
|
|
15779
|
+
return join10(agentBaseDir(agent), agent, taskListId);
|
|
15264
15780
|
}
|
|
15265
15781
|
function readAgentTask(dir, filename) {
|
|
15266
|
-
return readJsonFile(
|
|
15782
|
+
return readJsonFile(join10(dir, filename));
|
|
15267
15783
|
}
|
|
15268
15784
|
function writeAgentTask(dir, task) {
|
|
15269
|
-
writeJsonFile(
|
|
15785
|
+
writeJsonFile(join10(dir, `${task.id}.json`), task);
|
|
15270
15786
|
}
|
|
15271
15787
|
function taskToAgentTask(task, externalId, existingMeta) {
|
|
15272
15788
|
return {
|
|
@@ -15291,7 +15807,7 @@ function metadataKey(agent) {
|
|
|
15291
15807
|
}
|
|
15292
15808
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
15293
15809
|
const dir = getTaskListDir2(agent, taskListId);
|
|
15294
|
-
if (!
|
|
15810
|
+
if (!existsSync10(dir))
|
|
15295
15811
|
ensureDir2(dir);
|
|
15296
15812
|
const filter = {};
|
|
15297
15813
|
if (projectId)
|
|
@@ -15300,7 +15816,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
15300
15816
|
const existingByTodosId = new Map;
|
|
15301
15817
|
const files = listJsonFiles(dir);
|
|
15302
15818
|
for (const f of files) {
|
|
15303
|
-
const path =
|
|
15819
|
+
const path = join10(dir, f);
|
|
15304
15820
|
const at = readAgentTask(dir, f);
|
|
15305
15821
|
if (at?.metadata?.["todos_id"]) {
|
|
15306
15822
|
existingByTodosId.set(at.metadata["todos_id"], { task: at, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -15374,7 +15890,7 @@ function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
15374
15890
|
}
|
|
15375
15891
|
function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
15376
15892
|
const dir = getTaskListDir2(agent, taskListId);
|
|
15377
|
-
if (!
|
|
15893
|
+
if (!existsSync10(dir)) {
|
|
15378
15894
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
15379
15895
|
}
|
|
15380
15896
|
const files = listJsonFiles(dir);
|
|
@@ -15393,7 +15909,7 @@ function pullFromAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
|
15393
15909
|
}
|
|
15394
15910
|
for (const f of files) {
|
|
15395
15911
|
try {
|
|
15396
|
-
const filePath =
|
|
15912
|
+
const filePath = join10(dir, f);
|
|
15397
15913
|
const at = readAgentTask(dir, f);
|
|
15398
15914
|
if (!at)
|
|
15399
15915
|
continue;
|
|
@@ -15546,8 +16062,8 @@ __export(exports_project_bootstrap, {
|
|
|
15546
16062
|
discoverProjectWorkspace: () => discoverProjectWorkspace,
|
|
15547
16063
|
bootstrapProject: () => bootstrapProject
|
|
15548
16064
|
});
|
|
15549
|
-
import { existsSync as
|
|
15550
|
-
import { basename as basename4, dirname as dirname6, resolve as
|
|
16065
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
16066
|
+
import { basename as basename4, dirname as dirname6, resolve as resolve11 } from "path";
|
|
15551
16067
|
function safeStat(path) {
|
|
15552
16068
|
try {
|
|
15553
16069
|
return statSync3(path);
|
|
@@ -15556,7 +16072,7 @@ function safeStat(path) {
|
|
|
15556
16072
|
}
|
|
15557
16073
|
}
|
|
15558
16074
|
function canonicalPath(input) {
|
|
15559
|
-
const resolved =
|
|
16075
|
+
const resolved = resolve11(input);
|
|
15560
16076
|
const stats = safeStat(resolved);
|
|
15561
16077
|
if (stats?.isFile())
|
|
15562
16078
|
return dirname6(resolved);
|
|
@@ -15565,7 +16081,7 @@ function canonicalPath(input) {
|
|
|
15565
16081
|
function findUp(start, marker) {
|
|
15566
16082
|
let current = canonicalPath(start);
|
|
15567
16083
|
while (true) {
|
|
15568
|
-
if (
|
|
16084
|
+
if (existsSync11(resolve11(current, marker)))
|
|
15569
16085
|
return current;
|
|
15570
16086
|
const parent = dirname6(current);
|
|
15571
16087
|
if (parent === current)
|
|
@@ -15576,11 +16092,11 @@ function findUp(start, marker) {
|
|
|
15576
16092
|
function readPackageJson(path) {
|
|
15577
16093
|
if (!path)
|
|
15578
16094
|
return null;
|
|
15579
|
-
const file =
|
|
15580
|
-
if (!
|
|
16095
|
+
const file = resolve11(path, "package.json");
|
|
16096
|
+
if (!existsSync11(file))
|
|
15581
16097
|
return null;
|
|
15582
16098
|
try {
|
|
15583
|
-
const parsed = JSON.parse(
|
|
16099
|
+
const parsed = JSON.parse(readFileSync6(file, "utf-8"));
|
|
15584
16100
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
15585
16101
|
} catch {
|
|
15586
16102
|
return null;
|
|
@@ -15599,7 +16115,7 @@ function workspaceMarker(root, rootPackage) {
|
|
|
15599
16115
|
if (rootPackage?.workspaces)
|
|
15600
16116
|
markers.push("package.json#workspaces");
|
|
15601
16117
|
for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
|
|
15602
|
-
if (
|
|
16118
|
+
if (existsSync11(resolve11(root, marker)))
|
|
15603
16119
|
markers.push(marker);
|
|
15604
16120
|
}
|
|
15605
16121
|
const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
|
|
@@ -21122,9 +21638,9 @@ __export(exports_extract, {
|
|
|
21122
21638
|
buildCodebaseIndex: () => buildCodebaseIndex,
|
|
21123
21639
|
EXTRACT_TAGS: () => EXTRACT_TAGS
|
|
21124
21640
|
});
|
|
21125
|
-
import { existsSync as
|
|
21641
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7, statSync as statSync4 } from "fs";
|
|
21126
21642
|
import { createHash as createHash3 } from "crypto";
|
|
21127
|
-
import { relative as relative3, resolve as
|
|
21643
|
+
import { relative as relative3, resolve as resolve12, join as join11 } from "path";
|
|
21128
21644
|
function stableHash(value) {
|
|
21129
21645
|
return createHash3("sha256").update(value).digest("hex");
|
|
21130
21646
|
}
|
|
@@ -21132,12 +21648,12 @@ function normalizePathForMatch(value) {
|
|
|
21132
21648
|
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
21133
21649
|
}
|
|
21134
21650
|
function readGitignorePatterns(basePath) {
|
|
21135
|
-
const root = statSync4(basePath).isFile() ?
|
|
21136
|
-
const gitignorePath =
|
|
21137
|
-
if (!
|
|
21651
|
+
const root = statSync4(basePath).isFile() ? resolve12(basePath, "..") : basePath;
|
|
21652
|
+
const gitignorePath = join11(root, ".gitignore");
|
|
21653
|
+
if (!existsSync12(gitignorePath))
|
|
21138
21654
|
return [];
|
|
21139
21655
|
try {
|
|
21140
|
-
return
|
|
21656
|
+
return readFileSync7(gitignorePath, "utf-8").split(`
|
|
21141
21657
|
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!"));
|
|
21142
21658
|
} catch {
|
|
21143
21659
|
return [];
|
|
@@ -21268,7 +21784,7 @@ function collectFiles(basePath, extensions, excludes, respectGitignore) {
|
|
|
21268
21784
|
return files.sort();
|
|
21269
21785
|
}
|
|
21270
21786
|
function buildCodebaseIndex(options) {
|
|
21271
|
-
const basePath =
|
|
21787
|
+
const basePath = resolve12(options.path);
|
|
21272
21788
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
21273
21789
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
21274
21790
|
const excludes = options.exclude || [];
|
|
@@ -21276,10 +21792,10 @@ function buildCodebaseIndex(options) {
|
|
|
21276
21792
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
21277
21793
|
const indexed = [];
|
|
21278
21794
|
for (const file of files) {
|
|
21279
|
-
const fullPath = statSync4(basePath).isFile() ? basePath :
|
|
21795
|
+
const fullPath = statSync4(basePath).isFile() ? basePath : join11(basePath, file);
|
|
21280
21796
|
try {
|
|
21281
|
-
const source =
|
|
21282
|
-
const relPath = statSync4(basePath).isFile() ? relative3(
|
|
21797
|
+
const source = readFileSync7(fullPath, "utf-8");
|
|
21798
|
+
const relPath = statSync4(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
21283
21799
|
indexed.push({
|
|
21284
21800
|
file: relPath,
|
|
21285
21801
|
checksum: stableHash(source).slice(0, 24),
|
|
@@ -21299,7 +21815,7 @@ function buildCodebaseIndex(options) {
|
|
|
21299
21815
|
};
|
|
21300
21816
|
}
|
|
21301
21817
|
function extractTodos(options, db) {
|
|
21302
|
-
const basePath =
|
|
21818
|
+
const basePath = resolve12(options.path);
|
|
21303
21819
|
const tags = options.patterns || [...EXTRACT_TAGS];
|
|
21304
21820
|
const extensions = options.extensions ? new Set(options.extensions.map((e) => e.startsWith(".") ? e : `.${e}`)) : DEFAULT_EXTENSIONS;
|
|
21305
21821
|
const excludes = options.exclude || [];
|
|
@@ -21307,10 +21823,10 @@ function extractTodos(options, db) {
|
|
|
21307
21823
|
const files = collectFiles(basePath, extensions, excludes, respectGitignore);
|
|
21308
21824
|
const allComments = [];
|
|
21309
21825
|
for (const file of files) {
|
|
21310
|
-
const fullPath = statSync4(basePath).isFile() ? basePath :
|
|
21826
|
+
const fullPath = statSync4(basePath).isFile() ? basePath : join11(basePath, file);
|
|
21311
21827
|
try {
|
|
21312
|
-
const source =
|
|
21313
|
-
const relPath = statSync4(basePath).isFile() ? relative3(
|
|
21828
|
+
const source = readFileSync7(fullPath, "utf-8");
|
|
21829
|
+
const relPath = statSync4(basePath).isFile() ? relative3(resolve12(basePath, ".."), fullPath) : file;
|
|
21314
21830
|
const comments = extractFromSource(source, relPath, tags);
|
|
21315
21831
|
allComments.push(...comments);
|
|
21316
21832
|
} catch {}
|
|
@@ -21404,7 +21920,7 @@ async function watchSourceTodos(options, onRun) {
|
|
|
21404
21920
|
const interval = Math.max(100, options.interval_ms || 2000);
|
|
21405
21921
|
const once = options.once !== false && (!options.max_runs || options.max_runs <= 1);
|
|
21406
21922
|
const maxRuns = options.max_runs ?? (once ? 1 : Number.POSITIVE_INFINITY);
|
|
21407
|
-
const root =
|
|
21923
|
+
const root = resolve12(options.path);
|
|
21408
21924
|
const runs = [];
|
|
21409
21925
|
let previous = new Map;
|
|
21410
21926
|
for (let runNumber = 1;runNumber <= maxRuns; runNumber++) {
|
|
@@ -21705,6 +22221,36 @@ function prepareValue(column, value) {
|
|
|
21705
22221
|
return JSON.stringify(value ?? (column === "tags" || column === "files_changed" ? [] : {}));
|
|
21706
22222
|
return value === undefined ? null : value;
|
|
21707
22223
|
}
|
|
22224
|
+
function slugifyPlanValue(value) {
|
|
22225
|
+
return typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") : "";
|
|
22226
|
+
}
|
|
22227
|
+
function planSlugBase3(plan) {
|
|
22228
|
+
return slugifyPlanValue(plan.slug) || slugifyPlanValue(plan.name) || "plan";
|
|
22229
|
+
}
|
|
22230
|
+
function planSlugScope(projectId) {
|
|
22231
|
+
return typeof projectId === "string" && projectId ? projectId : "__global__";
|
|
22232
|
+
}
|
|
22233
|
+
function planSlugKey(projectId, slug) {
|
|
22234
|
+
return `${planSlugScope(projectId)}:${slug}`;
|
|
22235
|
+
}
|
|
22236
|
+
function normalizeBridgePlanSlugs(plans, db) {
|
|
22237
|
+
const existingRows = db.query("SELECT id, project_id, slug FROM plans WHERE slug IS NOT NULL").all();
|
|
22238
|
+
const existingIds = new Set(existingRows.map((row) => row.id));
|
|
22239
|
+
const used = new Set(existingRows.filter((row) => row.slug).map((row) => planSlugKey(row.project_id, row.slug)));
|
|
22240
|
+
return plans.map((plan) => {
|
|
22241
|
+
if (existingIds.has(plan.id))
|
|
22242
|
+
return plan;
|
|
22243
|
+
const base = planSlugBase3(plan);
|
|
22244
|
+
let candidate = base;
|
|
22245
|
+
let suffix = 2;
|
|
22246
|
+
while (used.has(planSlugKey(plan.project_id, candidate))) {
|
|
22247
|
+
candidate = `${base}-${suffix}`;
|
|
22248
|
+
suffix += 1;
|
|
22249
|
+
}
|
|
22250
|
+
used.add(planSlugKey(plan.project_id, candidate));
|
|
22251
|
+
return { ...plan, slug: candidate };
|
|
22252
|
+
});
|
|
22253
|
+
}
|
|
21708
22254
|
function insertRecord(db, tableKey, row) {
|
|
21709
22255
|
const table = tableByKey[tableKey];
|
|
21710
22256
|
const columns = insertColumns[tableKey];
|
|
@@ -21841,6 +22387,7 @@ function importLocalBridgeBundle(bundle, options = {}, db) {
|
|
|
21841
22387
|
const conflictStrategy = options.conflictStrategy ?? "skip";
|
|
21842
22388
|
const data = {
|
|
21843
22389
|
...bundle.data,
|
|
22390
|
+
plans: normalizeBridgePlanSlugs(bundle.data.plans, d),
|
|
21844
22391
|
tasks: sortedTasks(bundle.data.tasks),
|
|
21845
22392
|
saved_views: bundle.data.saved_views ?? [],
|
|
21846
22393
|
task_boards: bundle.data.task_boards ?? [],
|
|
@@ -21925,7 +22472,7 @@ var init_local_bridge = __esm(() => {
|
|
|
21925
22472
|
insertColumns = {
|
|
21926
22473
|
projects: ["id", "name", "path", "description", "task_list_id", "task_prefix", "task_counter", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
21927
22474
|
task_lists: ["id", "project_id", "slug", "name", "description", "metadata", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
21928
|
-
plans: ["id", "project_id", "task_list_id", "agent_id", "name", "description", "status", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
22475
|
+
plans: ["id", "slug", "project_id", "task_list_id", "agent_id", "name", "description", "status", "created_at", "updated_at", "machine_id", "synced_at"],
|
|
21929
22476
|
tasks: [
|
|
21930
22477
|
"id",
|
|
21931
22478
|
"short_id",
|
|
@@ -22575,7 +23122,7 @@ __export(exports_project_commands, {
|
|
|
22575
23122
|
registerProjectCommands: () => registerProjectCommands
|
|
22576
23123
|
});
|
|
22577
23124
|
import chalk4 from "chalk";
|
|
22578
|
-
import { basename as basename5, resolve as
|
|
23125
|
+
import { basename as basename5, resolve as resolve13 } from "path";
|
|
22579
23126
|
function collectOption(value, previous = []) {
|
|
22580
23127
|
return [...previous, value];
|
|
22581
23128
|
}
|
|
@@ -22917,7 +23464,7 @@ function registerProjectCommands(program2) {
|
|
|
22917
23464
|
program2.command("projects").description("List and manage projects").option("--add <path>", "Register a project by path").option("--name <name>", "Project name (with --add)").option("--task-list-id <id>", "Custom task list ID (with --add)").action(async (opts) => {
|
|
22918
23465
|
const globalOpts = program2.opts();
|
|
22919
23466
|
if (opts.add) {
|
|
22920
|
-
const projectPath =
|
|
23467
|
+
const projectPath = resolve13(opts.add);
|
|
22921
23468
|
const name = opts.name || basename5(projectPath);
|
|
22922
23469
|
const existing = getProjectByPath(projectPath);
|
|
22923
23470
|
let project;
|
|
@@ -23025,7 +23572,7 @@ function registerProjectCommands(program2) {
|
|
|
23025
23572
|
console.error(chalk4.red(`Project not found: ${projectId}`));
|
|
23026
23573
|
process.exit(1);
|
|
23027
23574
|
}
|
|
23028
|
-
const entry = setMachineLocalPath2(resolved,
|
|
23575
|
+
const entry = setMachineLocalPath2(resolved, resolve13(projectPath));
|
|
23029
23576
|
if (useJson) {
|
|
23030
23577
|
output(entry, true);
|
|
23031
23578
|
} else {
|
|
@@ -23092,7 +23639,7 @@ function registerProjectCommands(program2) {
|
|
|
23092
23639
|
const patterns = opts.pattern ? opts.pattern.split(",").map((t) => t.trim().toUpperCase()) : undefined;
|
|
23093
23640
|
const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
|
|
23094
23641
|
const result = extractTodos2({
|
|
23095
|
-
path:
|
|
23642
|
+
path: resolve13(scanPath),
|
|
23096
23643
|
patterns,
|
|
23097
23644
|
project_id: projectId,
|
|
23098
23645
|
task_list_id: taskListId,
|
|
@@ -23153,7 +23700,7 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
23153
23700
|
const taskListId = opts.list ? resolveTaskListId(opts.list) : undefined;
|
|
23154
23701
|
const maxRuns = opts.maxRuns ? parseInt(opts.maxRuns, 10) : 1;
|
|
23155
23702
|
const result = await watchSourceTodos2({
|
|
23156
|
-
path:
|
|
23703
|
+
path: resolve13(scanPath),
|
|
23157
23704
|
patterns,
|
|
23158
23705
|
project_id: projectId,
|
|
23159
23706
|
task_list_id: taskListId,
|
|
@@ -23189,8 +23736,8 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
23189
23736
|
const projectId = autoProject(globalOpts);
|
|
23190
23737
|
const writeOutput = async (content) => {
|
|
23191
23738
|
if (opts.output) {
|
|
23192
|
-
const { writeFileSync:
|
|
23193
|
-
|
|
23739
|
+
const { writeFileSync: writeFileSync6 } = await import("fs");
|
|
23740
|
+
writeFileSync6(resolve13(opts.output), content.endsWith(`
|
|
23194
23741
|
`) ? content : `${content}
|
|
23195
23742
|
`);
|
|
23196
23743
|
} else {
|
|
@@ -23205,12 +23752,12 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
23205
23752
|
const exported = opts.encrypt ? createEncryptedBridgeBundle2(bundle, { profile: opts.encryptionProfile }) : bundle;
|
|
23206
23753
|
const json = JSON.stringify(exported, null, 2);
|
|
23207
23754
|
await writeOutput(json);
|
|
23208
|
-
emitLocalEventHooksQuiet3({ type: "export.finished", payload: { format: "bridge", encrypted: Boolean(opts.encrypt), project_id: projectId, output: opts.output ?
|
|
23755
|
+
emitLocalEventHooksQuiet3({ type: "export.finished", payload: { format: "bridge", encrypted: Boolean(opts.encrypt), project_id: projectId, output: opts.output ? resolve13(opts.output) : null, stats: bundle.stats } });
|
|
23209
23756
|
if (!opts.encrypt && !opts.allowPlaintextSensitive) {
|
|
23210
23757
|
console.error(chalk4.yellow("Warning: bridge exports are plaintext JSON. Use --encrypt for sensitive metadata, evidence, and artifact bundles."));
|
|
23211
23758
|
}
|
|
23212
23759
|
if (opts.output && !globalOpts.json) {
|
|
23213
|
-
console.log(chalk4.green(`${opts.encrypt ? "Encrypted bridge export" : "Bridge export"} written to ${
|
|
23760
|
+
console.log(chalk4.green(`${opts.encrypt ? "Encrypted bridge export" : "Bridge export"} written to ${resolve13(opts.output)}`));
|
|
23214
23761
|
}
|
|
23215
23762
|
return;
|
|
23216
23763
|
}
|
|
@@ -23223,21 +23770,21 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
23223
23770
|
await writeOutput(JSON.stringify(tasks, null, 2));
|
|
23224
23771
|
}
|
|
23225
23772
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
23226
|
-
emitLocalEventHooksQuiet2({ type: "export.finished", payload: { format: opts.format, project_id: projectId, output: opts.output ?
|
|
23773
|
+
emitLocalEventHooksQuiet2({ type: "export.finished", payload: { format: opts.format, project_id: projectId, output: opts.output ? resolve13(opts.output) : null, count: exportedCount } });
|
|
23227
23774
|
});
|
|
23228
23775
|
program2.command("bridge-import <file>").description("Dry-run or apply a local hasna/todos bridge export bundle").option("--apply", "Apply the import. Defaults to dry-run.").option("--decrypt", "Decrypt an encrypted bridge export before importing").option("--resolve-conflicts", "Safely merge existing local tasks by filling blank fields, unioning tags, and recording unresolved divergences").action(async (file, opts) => {
|
|
23229
23776
|
const globalOpts = program2.opts();
|
|
23230
23777
|
try {
|
|
23231
|
-
const { readFileSync:
|
|
23778
|
+
const { readFileSync: readFileSync8 } = await import("fs");
|
|
23232
23779
|
const { importLocalBridgeBundle: importLocalBridgeBundle2 } = await Promise.resolve().then(() => (init_local_bridge(), exports_local_bridge));
|
|
23233
23780
|
const { decryptBridgeBundle: decryptBridgeBundle2, isEncryptedBridgeBundle: isEncryptedBridgeBundle2 } = await Promise.resolve().then(() => (init_local_encryption(), exports_local_encryption));
|
|
23234
|
-
const parsed = JSON.parse(
|
|
23781
|
+
const parsed = JSON.parse(readFileSync8(resolve13(file), "utf-8"));
|
|
23235
23782
|
const bundle = isEncryptedBridgeBundle2(parsed) ? opts.decrypt ? decryptBridgeBundle2(parsed) : (() => {
|
|
23236
23783
|
throw new Error("Bridge bundle is encrypted. Re-run with --decrypt and the configured key environment variable set.");
|
|
23237
23784
|
})() : parsed;
|
|
23238
23785
|
const result = importLocalBridgeBundle2(bundle, { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
23239
23786
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
23240
|
-
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file:
|
|
23787
|
+
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve13(file), dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, conflicts: result.conflicts.length, issues: result.issues.length } });
|
|
23241
23788
|
if (globalOpts.json) {
|
|
23242
23789
|
output(result, true);
|
|
23243
23790
|
return;
|
|
@@ -23265,11 +23812,11 @@ Indexed ${result.index.files.length} file(s), ${result.index.total_symbols} symb
|
|
|
23265
23812
|
program2.command("todos-md-import <file>").alias("markdown-import").alias("import-md").description("Dry-run or apply a local todos.md Markdown import").option("--apply", "Apply the import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge embedded bridge task conflicts while preserving local divergent fields").action(async (file, opts) => {
|
|
23266
23813
|
const globalOpts = program2.opts();
|
|
23267
23814
|
try {
|
|
23268
|
-
const { readFileSync:
|
|
23815
|
+
const { readFileSync: readFileSync8 } = await import("fs");
|
|
23269
23816
|
const { importTodosMarkdown: importTodosMarkdown2 } = await Promise.resolve().then(() => (init_todos_md(), exports_todos_md));
|
|
23270
|
-
const result = importTodosMarkdown2(
|
|
23817
|
+
const result = importTodosMarkdown2(readFileSync8(resolve13(file), "utf-8"), { dryRun: !opts.apply, conflictStrategy: opts.resolveConflicts ? "safe_merge" : "skip" });
|
|
23271
23818
|
const { emitLocalEventHooksQuiet: emitLocalEventHooksQuiet2 } = await Promise.resolve().then(() => (init_event_hooks(), exports_event_hooks));
|
|
23272
|
-
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file:
|
|
23819
|
+
emitLocalEventHooksQuiet2({ type: "import.finished", payload: { file: resolve13(file), format: "todos.md", dry_run: result.dry_run, ok: result.ok, inserted: result.inserted, skipped: result.skipped, issues: result.issues.length } });
|
|
23273
23820
|
if (globalOpts.json) {
|
|
23274
23821
|
output(result, true);
|
|
23275
23822
|
return;
|
|
@@ -24397,7 +24944,7 @@ __export(exports_retention_cleanup, {
|
|
|
24397
24944
|
applyRetentionCleanup: () => applyRetentionCleanup,
|
|
24398
24945
|
RETENTION_CLEANUP_CONFIRMATION: () => RETENTION_CLEANUP_CONFIRMATION
|
|
24399
24946
|
});
|
|
24400
|
-
import { existsSync as
|
|
24947
|
+
import { existsSync as existsSync13, unlinkSync } from "fs";
|
|
24401
24948
|
function normalizeScopes(scopes) {
|
|
24402
24949
|
if (!scopes || scopes.length === 0)
|
|
24403
24950
|
return [...ALL_SCOPES];
|
|
@@ -24600,7 +25147,7 @@ function applyRetentionCleanup(input, db) {
|
|
|
24600
25147
|
for (const artifact of report.candidates.artifact_files) {
|
|
24601
25148
|
try {
|
|
24602
25149
|
const path = artifactStorePath(artifact.relative_path);
|
|
24603
|
-
if (!
|
|
25150
|
+
if (!existsSync13(path)) {
|
|
24604
25151
|
report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
|
|
24605
25152
|
continue;
|
|
24606
25153
|
}
|
|
@@ -25278,8 +25825,8 @@ __export(exports_local_extensions, {
|
|
|
25278
25825
|
discoverLocalExtensions: () => discoverLocalExtensions
|
|
25279
25826
|
});
|
|
25280
25827
|
import { createHash as createHash5, createVerify } from "crypto";
|
|
25281
|
-
import { existsSync as
|
|
25282
|
-
import { basename as basename6, join as
|
|
25828
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
25829
|
+
import { basename as basename6, join as join12, resolve as resolve14 } from "path";
|
|
25283
25830
|
function isObject(value) {
|
|
25284
25831
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
25285
25832
|
}
|
|
@@ -25360,7 +25907,7 @@ function normalizeManifest(input) {
|
|
|
25360
25907
|
};
|
|
25361
25908
|
}
|
|
25362
25909
|
function parseJson(path) {
|
|
25363
|
-
return JSON.parse(
|
|
25910
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
25364
25911
|
}
|
|
25365
25912
|
function sha2563(bytes) {
|
|
25366
25913
|
return `sha256:${createHash5("sha256").update(bytes).digest("hex")}`;
|
|
@@ -25537,14 +26084,14 @@ function verifyExtensionSignature(input) {
|
|
|
25537
26084
|
return verifier.verify(input.public_key, decodeSignature(input.signature));
|
|
25538
26085
|
}
|
|
25539
26086
|
function inspectExtensionSource(source2) {
|
|
25540
|
-
const resolved =
|
|
25541
|
-
if (!
|
|
26087
|
+
const resolved = resolve14(source2);
|
|
26088
|
+
if (!existsSync14(resolved))
|
|
25542
26089
|
throw new Error(`extension source not found: ${source2}`);
|
|
25543
26090
|
const stat = statSync5(resolved);
|
|
25544
|
-
const manifestPath = stat.isDirectory() ? [
|
|
26091
|
+
const manifestPath = stat.isDirectory() ? [join12(resolved, "todos.extension.json"), join12(resolved, "extension.json")].find(existsSync14) : resolved;
|
|
25545
26092
|
if (!manifestPath)
|
|
25546
26093
|
throw new Error(`extension directory ${source2} is missing todos.extension.json`);
|
|
25547
|
-
const raw =
|
|
26094
|
+
const raw = readFileSync8(manifestPath);
|
|
25548
26095
|
const parsed = parseJson(manifestPath);
|
|
25549
26096
|
const bundle = isObject(parsed) && isObject(parsed["manifest"]);
|
|
25550
26097
|
const manifest = normalizeManifest(bundle ? parsed["manifest"] : parsed);
|
|
@@ -25635,26 +26182,26 @@ function testExtensionCompatibility(sourceOrManifest) {
|
|
|
25635
26182
|
function projectExtensionSources(projectPath) {
|
|
25636
26183
|
if (!projectPath)
|
|
25637
26184
|
return [];
|
|
25638
|
-
const root =
|
|
26185
|
+
const root = resolve14(projectPath);
|
|
25639
26186
|
const candidates = [
|
|
25640
|
-
|
|
25641
|
-
|
|
26187
|
+
join12(root, "todos.extension.json"),
|
|
26188
|
+
join12(root, ".todos", "todos.extension.json")
|
|
25642
26189
|
];
|
|
25643
|
-
const extensionDir =
|
|
25644
|
-
if (
|
|
26190
|
+
const extensionDir = join12(root, ".todos", "extensions");
|
|
26191
|
+
if (existsSync14(extensionDir)) {
|
|
25645
26192
|
for (const entry of readdirSync3(extensionDir)) {
|
|
25646
26193
|
if (entry.startsWith("."))
|
|
25647
26194
|
continue;
|
|
25648
|
-
const full =
|
|
26195
|
+
const full = join12(extensionDir, entry);
|
|
25649
26196
|
if (statSync5(full).isDirectory() || entry.endsWith(".json"))
|
|
25650
26197
|
candidates.push(full);
|
|
25651
26198
|
}
|
|
25652
26199
|
}
|
|
25653
|
-
return candidates.filter(
|
|
26200
|
+
return candidates.filter(existsSync14);
|
|
25654
26201
|
}
|
|
25655
26202
|
function discoverLocalExtensions(options = {}) {
|
|
25656
26203
|
const config = loadConfig();
|
|
25657
|
-
const projectPath = options.project_path ?
|
|
26204
|
+
const projectPath = options.project_path ? resolve14(options.project_path) : null;
|
|
25658
26205
|
const configuredSources = [
|
|
25659
26206
|
...config.extension_sources || [],
|
|
25660
26207
|
...projectPath ? config.project_overrides?.[projectPath]?.extension_sources || [] : []
|
|
@@ -25662,7 +26209,7 @@ function discoverLocalExtensions(options = {}) {
|
|
|
25662
26209
|
const sources = Array.from(new Set([
|
|
25663
26210
|
...configuredSources,
|
|
25664
26211
|
...projectExtensionSources(projectPath || undefined)
|
|
25665
|
-
])).map((source2) => projectPath && !source2.startsWith("/") ?
|
|
26212
|
+
])).map((source2) => projectPath && !source2.startsWith("/") ? resolve14(projectPath, source2) : resolve14(source2));
|
|
25666
26213
|
const warnings = [];
|
|
25667
26214
|
const discovered = [];
|
|
25668
26215
|
for (const source2 of sources) {
|
|
@@ -25922,9 +26469,9 @@ __export(exports_policy_packs, {
|
|
|
25922
26469
|
getPolicyPack: () => getPolicyPack,
|
|
25923
26470
|
explainPolicyPack: () => explainPolicyPack
|
|
25924
26471
|
});
|
|
25925
|
-
import { relative as relative4, resolve as
|
|
26472
|
+
import { relative as relative4, resolve as resolve15 } from "path";
|
|
25926
26473
|
function normalizePath3(path) {
|
|
25927
|
-
return
|
|
26474
|
+
return resolve15(path);
|
|
25928
26475
|
}
|
|
25929
26476
|
function unique5(values) {
|
|
25930
26477
|
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
@@ -25979,7 +26526,7 @@ function commandMatches(commands, pattern) {
|
|
|
25979
26526
|
}
|
|
25980
26527
|
function pathMatches(paths, pattern, root) {
|
|
25981
26528
|
return paths.filter((path) => {
|
|
25982
|
-
const candidate = path.startsWith("/") ? path :
|
|
26529
|
+
const candidate = path.startsWith("/") ? path : resolve15(root, path);
|
|
25983
26530
|
if (!isPathInside3(root, candidate))
|
|
25984
26531
|
return matchesPattern3(path, pattern);
|
|
25985
26532
|
return matchesPattern3(path, pattern) || matchesPattern3(relative4(root, candidate), pattern);
|
|
@@ -26945,8 +27492,8 @@ var exports_doctor = {};
|
|
|
26945
27492
|
__export(exports_doctor, {
|
|
26946
27493
|
runTodosDoctor: () => runTodosDoctor
|
|
26947
27494
|
});
|
|
26948
|
-
import { chmodSync, copyFileSync, existsSync as
|
|
26949
|
-
import { basename as basename7, dirname as dirname7, join as
|
|
27495
|
+
import { chmodSync, copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync7, statSync as statSync6 } from "fs";
|
|
27496
|
+
import { basename as basename7, dirname as dirname7, join as join13 } from "path";
|
|
26950
27497
|
function tableExists(db, table) {
|
|
26951
27498
|
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
26952
27499
|
}
|
|
@@ -27040,7 +27587,7 @@ function findMissingProjectRoots(db) {
|
|
|
27040
27587
|
continue;
|
|
27041
27588
|
if (!row.path.startsWith("/"))
|
|
27042
27589
|
continue;
|
|
27043
|
-
if (!
|
|
27590
|
+
if (!existsSync15(row.path))
|
|
27044
27591
|
missing++;
|
|
27045
27592
|
}
|
|
27046
27593
|
return missing;
|
|
@@ -27100,16 +27647,16 @@ function databasePermissionsAreUnsafe(dbPath) {
|
|
|
27100
27647
|
function createBackup(dbPath) {
|
|
27101
27648
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
27102
27649
|
return;
|
|
27103
|
-
if (!
|
|
27650
|
+
if (!existsSync15(dbPath))
|
|
27104
27651
|
return;
|
|
27105
27652
|
const stamp = now().replace(/[:.]/g, "-");
|
|
27106
|
-
const backupDir =
|
|
27653
|
+
const backupDir = join13(dirname7(dbPath), `${basename7(dbPath)}.backup-${stamp}`);
|
|
27107
27654
|
const files = [];
|
|
27108
|
-
|
|
27655
|
+
mkdirSync7(backupDir, { recursive: true });
|
|
27109
27656
|
for (const source2 of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
27110
|
-
if (!
|
|
27657
|
+
if (!existsSync15(source2))
|
|
27111
27658
|
continue;
|
|
27112
|
-
const target =
|
|
27659
|
+
const target = join13(backupDir, basename7(source2));
|
|
27113
27660
|
copyFileSync(source2, target);
|
|
27114
27661
|
files.push(target);
|
|
27115
27662
|
}
|
|
@@ -27335,7 +27882,7 @@ var init_doctor = __esm(() => {
|
|
|
27335
27882
|
});
|
|
27336
27883
|
|
|
27337
27884
|
// src/server/routes.ts
|
|
27338
|
-
import { join as
|
|
27885
|
+
import { join as join14, resolve as resolve16, sep as sep2 } from "path";
|
|
27339
27886
|
function parseFieldsParam(url) {
|
|
27340
27887
|
const fieldsParam = url.searchParams.get("fields");
|
|
27341
27888
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -28020,6 +28567,7 @@ async function handleCreatePlan(req, _ctx, json2) {
|
|
|
28020
28567
|
return json2({ error: "Missing 'name'" }, 400);
|
|
28021
28568
|
const plan = createPlan({
|
|
28022
28569
|
name: body.name,
|
|
28570
|
+
slug: body.slug,
|
|
28023
28571
|
description: body.description,
|
|
28024
28572
|
project_id: body.project_id,
|
|
28025
28573
|
task_list_id: body.task_list_id,
|
|
@@ -28072,9 +28620,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
28072
28620
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
28073
28621
|
return null;
|
|
28074
28622
|
if (path !== "/") {
|
|
28075
|
-
const filePath =
|
|
28076
|
-
const resolvedFile =
|
|
28077
|
-
const resolvedBase =
|
|
28623
|
+
const filePath = join14(ctx.dashboardDir, path);
|
|
28624
|
+
const resolvedFile = resolve16(filePath);
|
|
28625
|
+
const resolvedBase = resolve16(ctx.dashboardDir);
|
|
28078
28626
|
if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
|
|
28079
28627
|
return json2({ error: "Forbidden" }, 403);
|
|
28080
28628
|
}
|
|
@@ -28082,7 +28630,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
28082
28630
|
if (res2)
|
|
28083
28631
|
return res2;
|
|
28084
28632
|
}
|
|
28085
|
-
const indexPath =
|
|
28633
|
+
const indexPath = join14(ctx.dashboardDir, "index.html");
|
|
28086
28634
|
const res = serveStaticFile2(indexPath);
|
|
28087
28635
|
if (res)
|
|
28088
28636
|
return res;
|
|
@@ -33077,8 +33625,8 @@ var exports_mention_resolver = {};
|
|
|
33077
33625
|
__export(exports_mention_resolver, {
|
|
33078
33626
|
resolveMentions: () => resolveMentions
|
|
33079
33627
|
});
|
|
33080
|
-
import { existsSync as
|
|
33081
|
-
import { basename as basename8, isAbsolute, join as
|
|
33628
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
|
|
33629
|
+
import { basename as basename8, isAbsolute, join as join15, relative as relative5, resolve as resolve17, sep as sep3 } from "path";
|
|
33082
33630
|
function blankResolution(parsed) {
|
|
33083
33631
|
return {
|
|
33084
33632
|
input: parsed.input,
|
|
@@ -33101,7 +33649,7 @@ function backlink(kind, key, label, target = key) {
|
|
|
33101
33649
|
return { kind, key, label, target };
|
|
33102
33650
|
}
|
|
33103
33651
|
function normalizeWorkspace(workspace) {
|
|
33104
|
-
return
|
|
33652
|
+
return resolve17(workspace || process.cwd());
|
|
33105
33653
|
}
|
|
33106
33654
|
function isInside(root, absolutePath) {
|
|
33107
33655
|
const rel = relative5(root, absolutePath);
|
|
@@ -33169,14 +33717,14 @@ function resolveFile(parsed, workspace) {
|
|
|
33169
33717
|
resolution.warnings.push("path is empty or escapes the workspace");
|
|
33170
33718
|
return resolution;
|
|
33171
33719
|
}
|
|
33172
|
-
const absolutePath =
|
|
33720
|
+
const absolutePath = resolve17(workspace, relPath);
|
|
33173
33721
|
if (!isInside(workspace, absolutePath)) {
|
|
33174
33722
|
resolution.path = relPath;
|
|
33175
33723
|
resolution.warnings.push("path escapes the workspace");
|
|
33176
33724
|
return resolution;
|
|
33177
33725
|
}
|
|
33178
33726
|
resolution.path = relPath;
|
|
33179
|
-
if (!
|
|
33727
|
+
if (!existsSync16(absolutePath)) {
|
|
33180
33728
|
resolution.warnings.push("file does not exist in the local workspace");
|
|
33181
33729
|
return resolution;
|
|
33182
33730
|
}
|
|
@@ -33186,7 +33734,7 @@ function resolveFile(parsed, workspace) {
|
|
|
33186
33734
|
return resolution;
|
|
33187
33735
|
}
|
|
33188
33736
|
if (parsed.line !== undefined) {
|
|
33189
|
-
const lineCount =
|
|
33737
|
+
const lineCount = readFileSync9(absolutePath, "utf-8").split(/\r?\n/).length;
|
|
33190
33738
|
if (parsed.line < 1 || parsed.line > lineCount) {
|
|
33191
33739
|
resolution.warnings.push(`line ${parsed.line} is outside the file range 1-${lineCount}`);
|
|
33192
33740
|
return resolution;
|
|
@@ -33209,7 +33757,7 @@ function walkSourceFiles(root, current = root, files = []) {
|
|
|
33209
33757
|
if (SKIP_DIRS3.has(entry.name))
|
|
33210
33758
|
continue;
|
|
33211
33759
|
}
|
|
33212
|
-
const absolutePath =
|
|
33760
|
+
const absolutePath = join15(current, entry.name);
|
|
33213
33761
|
if (entry.isDirectory()) {
|
|
33214
33762
|
if (!SKIP_DIRS3.has(entry.name))
|
|
33215
33763
|
walkSourceFiles(root, absolutePath, files);
|
|
@@ -33239,7 +33787,7 @@ function resolveSymbol(parsed, workspace, maxMatches) {
|
|
|
33239
33787
|
const pattern = symbolPattern(name);
|
|
33240
33788
|
const matches = [];
|
|
33241
33789
|
for (const file of walkSourceFiles(workspace)) {
|
|
33242
|
-
const lines =
|
|
33790
|
+
const lines = readFileSync9(file, "utf-8").split(/\r?\n/);
|
|
33243
33791
|
for (let index = 0;index < lines.length; index += 1) {
|
|
33244
33792
|
const line = lines[index];
|
|
33245
33793
|
const found = pattern.exec(line);
|
|
@@ -35973,8 +36521,8 @@ __export(exports_release_compatibility, {
|
|
|
35973
36521
|
createReleaseCompatibilityReport: () => createReleaseCompatibilityReport,
|
|
35974
36522
|
LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION: () => LOCAL_RELEASE_COMPATIBILITY_SCHEMA_VERSION
|
|
35975
36523
|
});
|
|
35976
|
-
import { readFileSync as
|
|
35977
|
-
import { join as
|
|
36524
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
36525
|
+
import { join as join16, resolve as resolve18 } from "path";
|
|
35978
36526
|
import { Database as Database2 } from "bun:sqlite";
|
|
35979
36527
|
function pass(id, message, details) {
|
|
35980
36528
|
return { id, status: "passed", message, details };
|
|
@@ -35986,7 +36534,7 @@ function warn(id, message, details) {
|
|
|
35986
36534
|
return { id, status: "warning", message, details };
|
|
35987
36535
|
}
|
|
35988
36536
|
function readPackageJson2(root) {
|
|
35989
|
-
return JSON.parse(
|
|
36537
|
+
return JSON.parse(readFileSync10(join16(root, "package.json"), "utf8"));
|
|
35990
36538
|
}
|
|
35991
36539
|
function sortedKeys(value) {
|
|
35992
36540
|
return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
|
|
@@ -36082,7 +36630,7 @@ function checkChangelog() {
|
|
|
36082
36630
|
];
|
|
36083
36631
|
}
|
|
36084
36632
|
function createReleaseCompatibilityReport(options = {}) {
|
|
36085
|
-
const root =
|
|
36633
|
+
const root = resolve18(options.root ?? process.cwd());
|
|
36086
36634
|
const packageJson = readPackageJson2(root);
|
|
36087
36635
|
const simulatedLevels = options.simulated_levels ?? defaultSimulationLevels();
|
|
36088
36636
|
const checks = [
|
|
@@ -37369,6 +37917,7 @@ Tasks:` : null,
|
|
|
37369
37917
|
if (shouldRegisterTool("create_plan")) {
|
|
37370
37918
|
server.tool("create_plan", "Create a new plan (sprint/milestone).", {
|
|
37371
37919
|
name: exports_external2.string().describe("Plan name"),
|
|
37920
|
+
slug: exports_external2.string().optional().describe("Readable plan slug"),
|
|
37372
37921
|
project_id: exports_external2.string().optional().describe("Project ID"),
|
|
37373
37922
|
description: exports_external2.string().optional(),
|
|
37374
37923
|
start_date: exports_external2.string().optional().describe("ISO date"),
|
|
@@ -41665,7 +42214,7 @@ __export(exports_verification_providers, {
|
|
|
41665
42214
|
getVerificationRecord: () => getVerificationRecord,
|
|
41666
42215
|
discoverVerificationProviderCapabilities: () => discoverVerificationProviderCapabilities
|
|
41667
42216
|
});
|
|
41668
|
-
import { existsSync as
|
|
42217
|
+
import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
|
|
41669
42218
|
function normalizeName6(name) {
|
|
41670
42219
|
const normalized = name.trim().toLowerCase();
|
|
41671
42220
|
if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
|
|
@@ -41762,7 +42311,7 @@ function classifyLog(text) {
|
|
|
41762
42311
|
async function sleep3(ms) {
|
|
41763
42312
|
if (ms <= 0)
|
|
41764
42313
|
return;
|
|
41765
|
-
await new Promise((
|
|
42314
|
+
await new Promise((resolve19) => setTimeout(resolve19, ms));
|
|
41766
42315
|
}
|
|
41767
42316
|
async function runCommandProvider(provider, input) {
|
|
41768
42317
|
const commandTemplate = input.command || provider.command;
|
|
@@ -41817,7 +42366,7 @@ Timed out after ${provider.timeout_ms}ms`);
|
|
|
41817
42366
|
};
|
|
41818
42367
|
}
|
|
41819
42368
|
function runCiLogProvider(input) {
|
|
41820
|
-
const text = input.log_text ?? (input.log_path &&
|
|
42369
|
+
const text = input.log_text ?? (input.log_path && existsSync17(input.log_path) ? readFileSync11(input.log_path, "utf-8") : "");
|
|
41821
42370
|
return {
|
|
41822
42371
|
status: classifyLog(text),
|
|
41823
42372
|
attempts: 1,
|
|
@@ -41829,7 +42378,7 @@ function runBrowserProvider(input) {
|
|
|
41829
42378
|
if (!input.artifact_path) {
|
|
41830
42379
|
return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
|
|
41831
42380
|
}
|
|
41832
|
-
if (!
|
|
42381
|
+
if (!existsSync17(input.artifact_path)) {
|
|
41833
42382
|
return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
|
|
41834
42383
|
}
|
|
41835
42384
|
return {
|
|
@@ -44111,9 +44660,9 @@ __export(exports_local_backups, {
|
|
|
44111
44660
|
LOCAL_BACKUP_CHECKSUM_ALGORITHM: () => LOCAL_BACKUP_CHECKSUM_ALGORITHM
|
|
44112
44661
|
});
|
|
44113
44662
|
import { createHash as createHash8 } from "crypto";
|
|
44114
|
-
import { readFileSync as
|
|
44115
|
-
import { dirname as dirname8, resolve as
|
|
44116
|
-
import { mkdirSync as
|
|
44663
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
44664
|
+
import { dirname as dirname8, resolve as resolve19 } from "path";
|
|
44665
|
+
import { mkdirSync as mkdirSync8 } from "fs";
|
|
44117
44666
|
function stableJson(value) {
|
|
44118
44667
|
if (value === null || typeof value !== "object")
|
|
44119
44668
|
return JSON.stringify(value);
|
|
@@ -44214,14 +44763,14 @@ function createLocalBackup(options = {}, db) {
|
|
|
44214
44763
|
return backup;
|
|
44215
44764
|
}
|
|
44216
44765
|
function writeLocalBackupFile(backup, outputPath) {
|
|
44217
|
-
const path =
|
|
44218
|
-
|
|
44219
|
-
|
|
44766
|
+
const path = resolve19(outputPath);
|
|
44767
|
+
mkdirSync8(dirname8(path), { recursive: true });
|
|
44768
|
+
writeFileSync6(path, `${JSON.stringify(backup, null, 2)}
|
|
44220
44769
|
`);
|
|
44221
44770
|
return path;
|
|
44222
44771
|
}
|
|
44223
44772
|
function readLocalBackupFile(path) {
|
|
44224
|
-
return JSON.parse(
|
|
44773
|
+
return JSON.parse(readFileSync12(resolve19(path), "utf-8"));
|
|
44225
44774
|
}
|
|
44226
44775
|
function verifyLocalBackup(value, options = {}, db) {
|
|
44227
44776
|
const verifiedAt = options.verified_at ?? now();
|
|
@@ -44417,8 +44966,8 @@ __export(exports_onboarding_fixtures, {
|
|
|
44417
44966
|
TODOS_ONBOARDING_FIXTURE_SOURCE: () => TODOS_ONBOARDING_FIXTURE_SOURCE,
|
|
44418
44967
|
TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION: () => TODOS_ONBOARDING_FIXTURE_LIBRARY_VERSION
|
|
44419
44968
|
});
|
|
44420
|
-
import { mkdirSync as
|
|
44421
|
-
import { join as
|
|
44969
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
44970
|
+
import { join as join17 } from "path";
|
|
44422
44971
|
function emptyData() {
|
|
44423
44972
|
return {
|
|
44424
44973
|
projects: [],
|
|
@@ -44529,6 +45078,7 @@ function createAgentProjectDemoBundle() {
|
|
|
44529
45078
|
});
|
|
44530
45079
|
data.plans.push({
|
|
44531
45080
|
id: ids.plan,
|
|
45081
|
+
slug: "ship-local-demo-workflow",
|
|
44532
45082
|
project_id: ids.project,
|
|
44533
45083
|
task_list_id: ids.list,
|
|
44534
45084
|
agent_id: "demo-agent",
|
|
@@ -44748,11 +45298,11 @@ function getOnboardingFixtureBundle(name = "agent-project-demo") {
|
|
|
44748
45298
|
return getOnboardingFixture(name).bundle;
|
|
44749
45299
|
}
|
|
44750
45300
|
function writeOnboardingFixtureFiles(directory) {
|
|
44751
|
-
|
|
45301
|
+
mkdirSync9(directory, { recursive: true });
|
|
44752
45302
|
const files = [];
|
|
44753
45303
|
for (const fixture of allFixtures()) {
|
|
44754
|
-
const path =
|
|
44755
|
-
|
|
45304
|
+
const path = join17(directory, `${fixture.summary.name}.bridge.json`);
|
|
45305
|
+
writeFileSync7(path, `${JSON.stringify(fixture.bundle, null, 2)}
|
|
44756
45306
|
`, "utf-8");
|
|
44757
45307
|
files.push(path);
|
|
44758
45308
|
}
|
|
@@ -45399,7 +45949,7 @@ __export(exports_agent_replay_simulator, {
|
|
|
45399
45949
|
renderAgentReplaySimulationMarkdown: () => renderAgentReplaySimulationMarkdown
|
|
45400
45950
|
});
|
|
45401
45951
|
import { createHash as createHash10 } from "crypto";
|
|
45402
|
-
import { readFileSync as
|
|
45952
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
45403
45953
|
function isObject2(value) {
|
|
45404
45954
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
45405
45955
|
}
|
|
@@ -45632,7 +46182,7 @@ function simulateAgentReplay(input, options = {}) {
|
|
|
45632
46182
|
};
|
|
45633
46183
|
}
|
|
45634
46184
|
function simulateAgentReplayFile(path, options = {}) {
|
|
45635
|
-
const parsed = JSON.parse(
|
|
46185
|
+
const parsed = JSON.parse(readFileSync13(path, "utf8"));
|
|
45636
46186
|
return simulateAgentReplay(parsed, options);
|
|
45637
46187
|
}
|
|
45638
46188
|
function renderAgentReplaySimulationMarkdown(simulation) {
|
|
@@ -50919,28 +51469,28 @@ __export(exports_environment_snapshots, {
|
|
|
50919
51469
|
captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
|
|
50920
51470
|
});
|
|
50921
51471
|
import { createHash as createHash12 } from "crypto";
|
|
50922
|
-
import { existsSync as
|
|
51472
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14, statSync as statSync8 } from "fs";
|
|
50923
51473
|
import { hostname as hostname2, platform, arch } from "os";
|
|
50924
|
-
import { dirname as dirname9, join as
|
|
51474
|
+
import { dirname as dirname9, join as join18, resolve as resolve20 } from "path";
|
|
50925
51475
|
import { tmpdir as tmpdir3 } from "os";
|
|
50926
51476
|
function sha2566(value) {
|
|
50927
51477
|
return createHash12("sha256").update(value).digest("hex");
|
|
50928
51478
|
}
|
|
50929
51479
|
function fileRecord(root, relativePath) {
|
|
50930
|
-
const path =
|
|
50931
|
-
if (!
|
|
51480
|
+
const path = join18(root, relativePath);
|
|
51481
|
+
if (!existsSync18(path))
|
|
50932
51482
|
return null;
|
|
50933
51483
|
const stat = statSync8(path);
|
|
50934
51484
|
if (!stat.isFile())
|
|
50935
51485
|
return null;
|
|
50936
|
-
const content =
|
|
51486
|
+
const content = readFileSync14(path);
|
|
50937
51487
|
return { path: relativePath, sha256: sha2566(content), size_bytes: content.length };
|
|
50938
51488
|
}
|
|
50939
51489
|
function manifestRecord(root, relativePath) {
|
|
50940
51490
|
const base = fileRecord(root, relativePath);
|
|
50941
51491
|
if (!base)
|
|
50942
51492
|
return null;
|
|
50943
|
-
const parsed = readJsonFile(
|
|
51493
|
+
const parsed = readJsonFile(join18(root, relativePath));
|
|
50944
51494
|
if (!parsed)
|
|
50945
51495
|
return { ...base, redacted: {} };
|
|
50946
51496
|
const redacted = redactValue({
|
|
@@ -51035,15 +51585,15 @@ function commandEnv(env, includeValues) {
|
|
|
51035
51585
|
function defaultSnapshotDir() {
|
|
51036
51586
|
const dbPath = getDatabasePath();
|
|
51037
51587
|
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
51038
|
-
return
|
|
51039
|
-
return
|
|
51588
|
+
return join18(tmpdir3(), "hasna-todos", "environment-snapshots");
|
|
51589
|
+
return join18(dirname9(resolve20(dbPath)), "environment-snapshots");
|
|
51040
51590
|
}
|
|
51041
51591
|
function snapshotWithId(snapshot) {
|
|
51042
51592
|
const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
|
|
51043
51593
|
return { id: `env_${digest}`, ...snapshot };
|
|
51044
51594
|
}
|
|
51045
51595
|
function captureEnvironmentSnapshot(input = {}) {
|
|
51046
|
-
const root =
|
|
51596
|
+
const root = resolve20(input.root || process.cwd());
|
|
51047
51597
|
const env = input.env || process.env;
|
|
51048
51598
|
const warnings = [];
|
|
51049
51599
|
const manifests = MANIFEST_FILES.map((file) => manifestRecord(root, file)).filter((file) => Boolean(file));
|
|
@@ -51083,13 +51633,13 @@ function captureEnvironmentSnapshot(input = {}) {
|
|
|
51083
51633
|
});
|
|
51084
51634
|
}
|
|
51085
51635
|
function writeEnvironmentSnapshot(snapshot, outputPath) {
|
|
51086
|
-
const path = outputPath ?
|
|
51636
|
+
const path = outputPath ? resolve20(outputPath) : join18(defaultSnapshotDir(), `${snapshot.id}.json`);
|
|
51087
51637
|
ensureDir2(dirname9(path));
|
|
51088
51638
|
writeJsonFile(path, snapshot);
|
|
51089
51639
|
return path;
|
|
51090
51640
|
}
|
|
51091
51641
|
function readEnvironmentSnapshot(path) {
|
|
51092
|
-
const snapshot = readJsonFile(
|
|
51642
|
+
const snapshot = readJsonFile(resolve20(path));
|
|
51093
51643
|
if (!snapshot || snapshot.schema_version !== 1 || typeof snapshot.id !== "string") {
|
|
51094
51644
|
throw new Error(`Invalid environment snapshot: ${path}`);
|
|
51095
51645
|
}
|
|
@@ -51614,27 +52164,27 @@ __export(exports_serve, {
|
|
|
51614
52164
|
SECURITY_HEADERS: () => SECURITY_HEADERS,
|
|
51615
52165
|
MIME_TYPES: () => MIME_TYPES
|
|
51616
52166
|
});
|
|
51617
|
-
import { existsSync as
|
|
51618
|
-
import { join as
|
|
52167
|
+
import { existsSync as existsSync19 } from "fs";
|
|
52168
|
+
import { join as join19, dirname as dirname10, extname } from "path";
|
|
51619
52169
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
51620
52170
|
function resolveDashboardDir() {
|
|
51621
52171
|
const candidates = [];
|
|
51622
52172
|
try {
|
|
51623
52173
|
const scriptDir = dirname10(fileURLToPath2(import.meta.url));
|
|
51624
|
-
candidates.push(
|
|
51625
|
-
candidates.push(
|
|
52174
|
+
candidates.push(join19(scriptDir, "..", "dashboard", "dist"));
|
|
52175
|
+
candidates.push(join19(scriptDir, "..", "..", "dashboard", "dist"));
|
|
51626
52176
|
} catch {}
|
|
51627
52177
|
if (process.argv[1]) {
|
|
51628
52178
|
const mainDir = dirname10(process.argv[1]);
|
|
51629
|
-
candidates.push(
|
|
51630
|
-
candidates.push(
|
|
52179
|
+
candidates.push(join19(mainDir, "..", "dashboard", "dist"));
|
|
52180
|
+
candidates.push(join19(mainDir, "..", "..", "dashboard", "dist"));
|
|
51631
52181
|
}
|
|
51632
|
-
candidates.push(
|
|
52182
|
+
candidates.push(join19(process.cwd(), "dashboard", "dist"));
|
|
51633
52183
|
for (const candidate of candidates) {
|
|
51634
|
-
if (
|
|
52184
|
+
if (existsSync19(candidate))
|
|
51635
52185
|
return candidate;
|
|
51636
52186
|
}
|
|
51637
|
-
return
|
|
52187
|
+
return join19(process.cwd(), "dashboard", "dist");
|
|
51638
52188
|
}
|
|
51639
52189
|
function getProvidedApiKey(req) {
|
|
51640
52190
|
const headerKey = req.headers.get("x-api-key");
|
|
@@ -51684,7 +52234,7 @@ function json(data, status = 200, headers) {
|
|
|
51684
52234
|
});
|
|
51685
52235
|
}
|
|
51686
52236
|
function serveStaticFile(filePath) {
|
|
51687
|
-
if (!
|
|
52237
|
+
if (!existsSync19(filePath))
|
|
51688
52238
|
return null;
|
|
51689
52239
|
const ext = extname(filePath);
|
|
51690
52240
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -51765,7 +52315,7 @@ data: ${data}
|
|
|
51765
52315
|
filteredSseClients.delete(client);
|
|
51766
52316
|
}
|
|
51767
52317
|
const dashboardDir = resolveDashboardDir();
|
|
51768
|
-
const dashboardExists =
|
|
52318
|
+
const dashboardExists = existsSync19(dashboardDir);
|
|
51769
52319
|
if (!dashboardExists) {
|
|
51770
52320
|
console.error(`
|
|
51771
52321
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -53595,12 +54145,12 @@ __export(exports_config_serve_commands, {
|
|
|
53595
54145
|
registerConfigServeCommands: () => registerConfigServeCommands
|
|
53596
54146
|
});
|
|
53597
54147
|
import chalk6 from "chalk";
|
|
53598
|
-
import { existsSync as
|
|
53599
|
-
import { dirname as dirname11, join as
|
|
54148
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
54149
|
+
import { dirname as dirname11, join as join20 } from "path";
|
|
53600
54150
|
function registerConfigServeCommands(program2) {
|
|
53601
54151
|
program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
|
|
53602
54152
|
const globalOpts = program2.opts();
|
|
53603
|
-
const configPath =
|
|
54153
|
+
const configPath = join20(getTodosGlobalDir(), "config.json");
|
|
53604
54154
|
if (opts.get) {
|
|
53605
54155
|
const config2 = loadConfig();
|
|
53606
54156
|
const keys = opts.get.split(".");
|
|
@@ -53626,7 +54176,7 @@ function registerConfigServeCommands(program2) {
|
|
|
53626
54176
|
}
|
|
53627
54177
|
let config2 = {};
|
|
53628
54178
|
try {
|
|
53629
|
-
config2 = JSON.parse(
|
|
54179
|
+
config2 = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
53630
54180
|
} catch {}
|
|
53631
54181
|
const keys = key.split(".");
|
|
53632
54182
|
let obj = config2;
|
|
@@ -53637,9 +54187,9 @@ function registerConfigServeCommands(program2) {
|
|
|
53637
54187
|
}
|
|
53638
54188
|
obj[keys[keys.length - 1]] = parsedValue;
|
|
53639
54189
|
const dir = dirname11(configPath);
|
|
53640
|
-
if (!
|
|
53641
|
-
|
|
53642
|
-
|
|
54190
|
+
if (!existsSync20(dir))
|
|
54191
|
+
mkdirSync10(dir, { recursive: true });
|
|
54192
|
+
writeFileSync8(configPath, JSON.stringify(config2, null, 2));
|
|
53643
54193
|
if (globalOpts.json) {
|
|
53644
54194
|
output({ key, value: parsedValue }, true);
|
|
53645
54195
|
} else {
|
|
@@ -53766,7 +54316,7 @@ function registerConfigServeCommands(program2) {
|
|
|
53766
54316
|
redaction.command("scan [text]").description("Scan text or a file for secret-like values without printing values").option("--file <path>", "File to scan").action(async (text2, opts) => {
|
|
53767
54317
|
const globalOpts = program2.opts();
|
|
53768
54318
|
const { listSecretFindings: listSecretFindings2 } = await Promise.resolve().then(() => (init_redaction(), exports_redaction));
|
|
53769
|
-
const value = opts.file ?
|
|
54319
|
+
const value = opts.file ? readFileSync15(opts.file, "utf-8") : text2 || "";
|
|
53770
54320
|
const findings = listSecretFindings2(value);
|
|
53771
54321
|
if (globalOpts.json) {
|
|
53772
54322
|
output({ ok: findings.length === 0, findings }, true);
|
|
@@ -55152,7 +55702,7 @@ __export(exports_query_commands, {
|
|
|
55152
55702
|
registerQueryCommands: () => registerQueryCommands
|
|
55153
55703
|
});
|
|
55154
55704
|
import chalk7 from "chalk";
|
|
55155
|
-
import { readFileSync as
|
|
55705
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
|
|
55156
55706
|
function parseJsonObjectOption2(value, label) {
|
|
55157
55707
|
if (!value)
|
|
55158
55708
|
return;
|
|
@@ -55811,9 +56361,9 @@ Repairs`));
|
|
|
55811
56361
|
const db = getDatabase();
|
|
55812
56362
|
const row = db.query("SELECT COUNT(*) as count FROM tasks").get();
|
|
55813
56363
|
const { statSync: statSync9 } = await import("fs");
|
|
55814
|
-
const { join:
|
|
56364
|
+
const { join: join21 } = await import("path");
|
|
55815
56365
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
55816
|
-
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] ||
|
|
56366
|
+
const dbPath = process.env["HASNA_TODOS_DB_PATH"] || process.env["TODOS_DB_PATH"] || join21(home, ".hasna", "todos", "todos.db");
|
|
55817
56367
|
let size = "unknown";
|
|
55818
56368
|
try {
|
|
55819
56369
|
size = `${(statSync9(dbPath).size / 1024 / 1024).toFixed(1)} MB`;
|
|
@@ -56430,7 +56980,7 @@ Repairs`));
|
|
|
56430
56980
|
const sessionId = opts.session || globalOpts.session || undefined;
|
|
56431
56981
|
try {
|
|
56432
56982
|
if (opts.import) {
|
|
56433
|
-
const bundle = JSON.parse(
|
|
56983
|
+
const bundle = JSON.parse(readFileSync16(opts.import, "utf-8"));
|
|
56434
56984
|
const result = importHandoffBundle(bundle, { apply: opts.apply }, db);
|
|
56435
56985
|
if (opts.json || globalOpts.json) {
|
|
56436
56986
|
console.log(JSON.stringify(result));
|
|
@@ -56446,7 +56996,7 @@ Repairs`));
|
|
|
56446
56996
|
const bundle = exportHandoffBundle(opts.export, db);
|
|
56447
56997
|
const json2 = JSON.stringify(bundle, null, 2);
|
|
56448
56998
|
if (opts.output) {
|
|
56449
|
-
|
|
56999
|
+
writeFileSync9(opts.output, `${json2}
|
|
56450
57000
|
`);
|
|
56451
57001
|
if (opts.json || globalOpts.json) {
|
|
56452
57002
|
console.log(JSON.stringify({ path: opts.output, handoff_id: bundle.handoff.id }));
|
|
@@ -56677,7 +57227,7 @@ Repairs`));
|
|
|
56677
57227
|
});
|
|
56678
57228
|
const content = format === "json" ? JSON.stringify(document, null, 2) : renderReleaseNotesMarkdown2(document);
|
|
56679
57229
|
if (opts.out) {
|
|
56680
|
-
|
|
57230
|
+
writeFileSync9(opts.out, content);
|
|
56681
57231
|
if (format !== "json")
|
|
56682
57232
|
console.log(chalk7.green(`Wrote release notes to ${opts.out}`));
|
|
56683
57233
|
return;
|
|
@@ -56792,7 +57342,7 @@ Repairs`));
|
|
|
56792
57342
|
redact: Boolean(opts.redact)
|
|
56793
57343
|
});
|
|
56794
57344
|
if (opts.out) {
|
|
56795
|
-
|
|
57345
|
+
writeFileSync9(opts.out, exported.content);
|
|
56796
57346
|
if (!(opts.json || globalOpts.json))
|
|
56797
57347
|
console.log(chalk7.green(`Wrote ${exported.events.length} events to ${opts.out}`));
|
|
56798
57348
|
}
|
|
@@ -56808,7 +57358,7 @@ Repairs`));
|
|
|
56808
57358
|
});
|
|
56809
57359
|
calendar.command("import <path>").description("Import VEVENT entries from an ICS file as local imported calendar items").option("-j, --json", "Output JSON").action((path, opts) => {
|
|
56810
57360
|
try {
|
|
56811
|
-
const result = importCalendarIcs(
|
|
57361
|
+
const result = importCalendarIcs(readFileSync16(path, "utf-8"));
|
|
56812
57362
|
if (opts.json || program2.opts().json) {
|
|
56813
57363
|
output(result, true);
|
|
56814
57364
|
return;
|
|
@@ -56947,7 +57497,7 @@ Repairs`));
|
|
|
56947
57497
|
const bundle = exportTaskBoardBundle(boardId);
|
|
56948
57498
|
const json2 = JSON.stringify(bundle, null, 2);
|
|
56949
57499
|
if (opts.out) {
|
|
56950
|
-
|
|
57500
|
+
writeFileSync9(opts.out, json2);
|
|
56951
57501
|
if (!(opts.json || program2.opts().json))
|
|
56952
57502
|
console.log(chalk7.green(`Wrote ${bundle.boards.length} board(s) to ${opts.out}`));
|
|
56953
57503
|
}
|
|
@@ -56959,7 +57509,7 @@ Repairs`));
|
|
|
56959
57509
|
});
|
|
56960
57510
|
board.command("import <path>").description("Import local board definitions from a JSON bundle").option("-j, --json", "Output JSON").action((path, opts) => {
|
|
56961
57511
|
try {
|
|
56962
|
-
const bundle = JSON.parse(
|
|
57512
|
+
const bundle = JSON.parse(readFileSync16(path, "utf-8"));
|
|
56963
57513
|
const result = importTaskBoardBundle(bundle);
|
|
56964
57514
|
if (opts.json || program2.opts().json) {
|
|
56965
57515
|
output(result, true);
|
|
@@ -57335,7 +57885,7 @@ Repairs`));
|
|
|
57335
57885
|
const { importExternalIssues: importExternalIssues2 } = await Promise.resolve().then(() => (init_external_issue_importers(), exports_external_issue_importers));
|
|
57336
57886
|
let body = text2 || "";
|
|
57337
57887
|
if (opts.file)
|
|
57338
|
-
body =
|
|
57888
|
+
body = readFileSync16(opts.file, "utf-8");
|
|
57339
57889
|
if (!body && !opts.url && !process.stdin.isTTY)
|
|
57340
57890
|
body = await Bun.stdin.text();
|
|
57341
57891
|
if (!body.trim() && !opts.url) {
|
|
@@ -57393,7 +57943,7 @@ Repairs`));
|
|
|
57393
57943
|
} = await Promise.resolve().then(() => (init_tester_issue_reports(), exports_tester_issue_reports));
|
|
57394
57944
|
let body = jsonText || "";
|
|
57395
57945
|
if (opts.file)
|
|
57396
|
-
body =
|
|
57946
|
+
body = readFileSync16(opts.file, "utf-8");
|
|
57397
57947
|
if (!body && !process.stdin.isTTY)
|
|
57398
57948
|
body = await Bun.stdin.text();
|
|
57399
57949
|
if (!body.trim()) {
|
|
@@ -57436,11 +57986,11 @@ Repairs`));
|
|
|
57436
57986
|
const inbox = program2.command("inbox").description("Capture local inbox items from pasted errors, CI logs, git context, files, or GitHub issue URLs");
|
|
57437
57987
|
inbox.command("add [text]").description("Create a local inbox item and linked task from text, stdin, or a file").option("--file <path>", "Read captured context from a file").option("--source-type <type>", "pasted_error, ci_log, git_context, github_issue, file, or other").option("--source-name <name>", "Human-readable source name").option("--source-url <url>", "Source URL, including GitHub issue URLs").option("--title <title>", "Task/inbox title").option("--priority <priority>", "Task priority").option("--tags <tags>", "Comma-separated extra tags").option("--metadata <json>", "Additional JSON metadata").option("--no-task", "Only store inbox item; do not create a linked task").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
|
|
57438
57988
|
const globalOpts = program2.opts();
|
|
57439
|
-
const { readFileSync:
|
|
57989
|
+
const { readFileSync: readFileSync17 } = await import("fs");
|
|
57440
57990
|
const { createInboxItem: createInboxItem2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
|
|
57441
57991
|
let body = text2 || "";
|
|
57442
57992
|
if (opts.file)
|
|
57443
|
-
body =
|
|
57993
|
+
body = readFileSync17(opts.file, "utf-8");
|
|
57444
57994
|
if (!body && !process.stdin.isTTY)
|
|
57445
57995
|
body = await Bun.stdin.text();
|
|
57446
57996
|
if (!body.trim()) {
|
|
@@ -57501,11 +58051,11 @@ ${diff}` : null].filter(Boolean).join(`
|
|
|
57501
58051
|
});
|
|
57502
58052
|
inbox.command("parse [text]").description("Preview or apply deterministic local natural-language task intake").option("--file <path>", "Read natural-language input from a file").option("--priority <priority>", "Default priority for parsed tasks", "medium").option("--project <id>", "Project ID for applied tasks").option("--list <id>", "Task list ID for applied tasks").option("--reference-date <iso>", "Reference date for due today/tomorrow/next week").option("--apply", "Create parsed tasks; default is dry-run preview").option("-j, --json", "Output as JSON").action(async (text2, opts) => {
|
|
57503
58053
|
const globalOpts = program2.opts();
|
|
57504
|
-
const { readFileSync:
|
|
58054
|
+
const { readFileSync: readFileSync17 } = await import("fs");
|
|
57505
58055
|
const { previewNaturalLanguageIntake: previewNaturalLanguageIntake2 } = await Promise.resolve().then(() => (init_natural_language_intake(), exports_natural_language_intake));
|
|
57506
58056
|
let body = text2 || "";
|
|
57507
58057
|
if (opts.file)
|
|
57508
|
-
body =
|
|
58058
|
+
body = readFileSync17(opts.file, "utf-8");
|
|
57509
58059
|
if (!body && !process.stdin.isTTY)
|
|
57510
58060
|
body = await Bun.stdin.text();
|
|
57511
58061
|
if (!body.trim()) {
|
|
@@ -57629,45 +58179,45 @@ __export(exports_mcp_hooks_commands, {
|
|
|
57629
58179
|
});
|
|
57630
58180
|
import chalk8 from "chalk";
|
|
57631
58181
|
import { execSync as execSync3 } from "child_process";
|
|
57632
|
-
import { existsSync as
|
|
57633
|
-
import { dirname as dirname12, join as
|
|
58182
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync10, mkdirSync as mkdirSync11, chmodSync as chmodSync2 } from "fs";
|
|
58183
|
+
import { dirname as dirname12, join as join21 } from "path";
|
|
57634
58184
|
function getMcpBinaryPath() {
|
|
57635
58185
|
try {
|
|
57636
58186
|
const p = execSync3("which todos-mcp", { encoding: "utf-8" }).trim();
|
|
57637
58187
|
if (p)
|
|
57638
58188
|
return p;
|
|
57639
58189
|
} catch {}
|
|
57640
|
-
const bunBin =
|
|
57641
|
-
if (
|
|
58190
|
+
const bunBin = join21(HOME2, ".bun", "bin", "todos-mcp");
|
|
58191
|
+
if (existsSync21(bunBin))
|
|
57642
58192
|
return bunBin;
|
|
57643
58193
|
return "todos-mcp";
|
|
57644
58194
|
}
|
|
57645
58195
|
function readJsonFile2(path) {
|
|
57646
|
-
if (!
|
|
58196
|
+
if (!existsSync21(path))
|
|
57647
58197
|
return {};
|
|
57648
58198
|
try {
|
|
57649
|
-
return JSON.parse(
|
|
58199
|
+
return JSON.parse(readFileSync17(path, "utf-8"));
|
|
57650
58200
|
} catch {
|
|
57651
58201
|
return {};
|
|
57652
58202
|
}
|
|
57653
58203
|
}
|
|
57654
58204
|
function writeJsonFile2(path, data) {
|
|
57655
58205
|
const dir = dirname12(path);
|
|
57656
|
-
if (!
|
|
57657
|
-
|
|
57658
|
-
|
|
58206
|
+
if (!existsSync21(dir))
|
|
58207
|
+
mkdirSync11(dir, { recursive: true });
|
|
58208
|
+
writeFileSync10(path, JSON.stringify(data, null, 2) + `
|
|
57659
58209
|
`);
|
|
57660
58210
|
}
|
|
57661
58211
|
function readTomlFile(path) {
|
|
57662
|
-
if (!
|
|
58212
|
+
if (!existsSync21(path))
|
|
57663
58213
|
return "";
|
|
57664
|
-
return
|
|
58214
|
+
return readFileSync17(path, "utf-8");
|
|
57665
58215
|
}
|
|
57666
58216
|
function writeTomlFile(path, content) {
|
|
57667
58217
|
const dir = dirname12(path);
|
|
57668
|
-
if (!
|
|
57669
|
-
|
|
57670
|
-
|
|
58218
|
+
if (!existsSync21(dir))
|
|
58219
|
+
mkdirSync11(dir, { recursive: true });
|
|
58220
|
+
writeFileSync10(path, content);
|
|
57671
58221
|
}
|
|
57672
58222
|
function removeTomlBlock(content, blockName) {
|
|
57673
58223
|
const lines = content.split(`
|
|
@@ -57731,7 +58281,7 @@ function unregisterClaude(_global) {
|
|
|
57731
58281
|
}
|
|
57732
58282
|
}
|
|
57733
58283
|
function registerCodex(binPath) {
|
|
57734
|
-
const configPath =
|
|
58284
|
+
const configPath = join21(HOME2, ".codex", "config.toml");
|
|
57735
58285
|
let content = readTomlFile(configPath);
|
|
57736
58286
|
content = removeTomlBlock(content, "mcp_servers.todos");
|
|
57737
58287
|
const block = `
|
|
@@ -57745,7 +58295,7 @@ args = []
|
|
|
57745
58295
|
console.log(chalk8.green(`Codex CLI: registered in ${configPath}`));
|
|
57746
58296
|
}
|
|
57747
58297
|
function unregisterCodex() {
|
|
57748
|
-
const configPath =
|
|
58298
|
+
const configPath = join21(HOME2, ".codex", "config.toml");
|
|
57749
58299
|
let content = readTomlFile(configPath);
|
|
57750
58300
|
if (!content.includes("[mcp_servers.todos]")) {
|
|
57751
58301
|
console.log(chalk8.dim(`Codex CLI: todos not found in ${configPath}`));
|
|
@@ -57757,7 +58307,7 @@ function unregisterCodex() {
|
|
|
57757
58307
|
console.log(chalk8.green(`Codex CLI: unregistered from ${configPath}`));
|
|
57758
58308
|
}
|
|
57759
58309
|
function registerGemini(binPath) {
|
|
57760
|
-
const configPath =
|
|
58310
|
+
const configPath = join21(HOME2, ".gemini", "settings.json");
|
|
57761
58311
|
const config = readJsonFile2(configPath);
|
|
57762
58312
|
if (!config["mcpServers"]) {
|
|
57763
58313
|
config["mcpServers"] = {};
|
|
@@ -57771,7 +58321,7 @@ function registerGemini(binPath) {
|
|
|
57771
58321
|
console.log(chalk8.green(`Gemini CLI: registered in ${configPath}`));
|
|
57772
58322
|
}
|
|
57773
58323
|
function unregisterGemini() {
|
|
57774
|
-
const configPath =
|
|
58324
|
+
const configPath = join21(HOME2, ".gemini", "settings.json");
|
|
57775
58325
|
const config = readJsonFile2(configPath);
|
|
57776
58326
|
const servers = config["mcpServers"];
|
|
57777
58327
|
if (!servers || !("todos" in servers)) {
|
|
@@ -57828,9 +58378,9 @@ function registerMcpHooksCommands(program2) {
|
|
|
57828
58378
|
if (p)
|
|
57829
58379
|
todosBin = p;
|
|
57830
58380
|
} catch {}
|
|
57831
|
-
const hooksDir =
|
|
57832
|
-
if (!
|
|
57833
|
-
|
|
58381
|
+
const hooksDir = join21(process.cwd(), ".claude", "hooks");
|
|
58382
|
+
if (!existsSync21(hooksDir))
|
|
58383
|
+
mkdirSync11(hooksDir, { recursive: true });
|
|
57834
58384
|
const hookScript = `#!/usr/bin/env bash
|
|
57835
58385
|
# Auto-generated by: todos hooks install
|
|
57836
58386
|
# Syncs todos with Claude Code task list on tool use events.
|
|
@@ -57854,11 +58404,11 @@ esac
|
|
|
57854
58404
|
|
|
57855
58405
|
exit 0
|
|
57856
58406
|
`;
|
|
57857
|
-
const hookPath =
|
|
57858
|
-
|
|
58407
|
+
const hookPath = join21(hooksDir, "todos-sync.sh");
|
|
58408
|
+
writeFileSync10(hookPath, hookScript);
|
|
57859
58409
|
execSync3(`chmod +x "${hookPath}"`);
|
|
57860
58410
|
console.log(chalk8.green(`Hook script created: ${hookPath}`));
|
|
57861
|
-
const settingsPath =
|
|
58411
|
+
const settingsPath = join21(process.cwd(), ".claude", "settings.json");
|
|
57862
58412
|
const settings = readJsonFile2(settingsPath);
|
|
57863
58413
|
if (!settings["hooks"]) {
|
|
57864
58414
|
settings["hooks"] = {};
|
|
@@ -58727,18 +59277,18 @@ Artifacts:`));
|
|
|
58727
59277
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
58728
59278
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
58729
59279
|
const marker = "# todos-auto-link";
|
|
58730
|
-
if (
|
|
58731
|
-
const existing =
|
|
59280
|
+
if (existsSync21(hookPath)) {
|
|
59281
|
+
const existing = readFileSync17(hookPath, "utf-8");
|
|
58732
59282
|
if (existing.includes(marker)) {
|
|
58733
59283
|
console.log(chalk8.yellow("Hook already installed."));
|
|
58734
59284
|
return;
|
|
58735
59285
|
}
|
|
58736
|
-
|
|
59286
|
+
writeFileSync10(hookPath, existing + `
|
|
58737
59287
|
${marker}
|
|
58738
59288
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
58739
59289
|
`);
|
|
58740
59290
|
} else {
|
|
58741
|
-
|
|
59291
|
+
writeFileSync10(hookPath, `#!/usr/bin/env bash
|
|
58742
59292
|
${marker}
|
|
58743
59293
|
$(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
58744
59294
|
`);
|
|
@@ -58755,11 +59305,11 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
58755
59305
|
const gitDir = execSync3("git rev-parse --git-dir", { encoding: "utf-8" }).trim();
|
|
58756
59306
|
const hookPath = `${gitDir}/hooks/post-commit`;
|
|
58757
59307
|
const marker = "# todos-auto-link";
|
|
58758
|
-
if (!
|
|
59308
|
+
if (!existsSync21(hookPath)) {
|
|
58759
59309
|
console.log(chalk8.dim("No post-commit hook found."));
|
|
58760
59310
|
return;
|
|
58761
59311
|
}
|
|
58762
|
-
const content =
|
|
59312
|
+
const content = readFileSync17(hookPath, "utf-8");
|
|
58763
59313
|
if (!content.includes(marker)) {
|
|
58764
59314
|
console.log(chalk8.dim("Hook not managed by todos."));
|
|
58765
59315
|
return;
|
|
@@ -58770,7 +59320,7 @@ $(dirname "$0")/../../scripts/post-commit-hook.sh
|
|
|
58770
59320
|
if (cleaned === "#!/usr/bin/env bash" || cleaned === "") {
|
|
58771
59321
|
(await import("fs")).unlinkSync(hookPath);
|
|
58772
59322
|
} else {
|
|
58773
|
-
|
|
59323
|
+
writeFileSync10(hookPath, cleaned + `
|
|
58774
59324
|
`);
|
|
58775
59325
|
}
|
|
58776
59326
|
console.log(chalk8.green("Post-commit hook removed."));
|
|
@@ -58939,9 +59489,9 @@ __export(exports_machines, {
|
|
|
58939
59489
|
});
|
|
58940
59490
|
import chalk10 from "chalk";
|
|
58941
59491
|
import { execSync as execSync4 } from "child_process";
|
|
58942
|
-
import { readFileSync as
|
|
59492
|
+
import { readFileSync as readFileSync18, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
58943
59493
|
import { tmpdir as tmpdir4 } from "os";
|
|
58944
|
-
import { join as
|
|
59494
|
+
import { join as join22 } from "path";
|
|
58945
59495
|
function getOrCreateLocalMachineName() {
|
|
58946
59496
|
return process.env["TODOS_MACHINE_NAME"] || __require("os").hostname() || "unknown";
|
|
58947
59497
|
}
|
|
@@ -58979,11 +59529,11 @@ function remoteTempPath(sshAddress) {
|
|
|
58979
59529
|
}
|
|
58980
59530
|
function readRemoteBridgeBundle(sshAddress) {
|
|
58981
59531
|
const remotePath = remoteTempPath(sshAddress);
|
|
58982
|
-
const localPath =
|
|
59532
|
+
const localPath = join22(tmpdir4(), `todos-bridge-pull-${uuid()}.json`);
|
|
58983
59533
|
try {
|
|
58984
59534
|
runSsh(sshAddress, `todos export --format bridge --allow-plaintext-sensitive --output ${shellQuote(remotePath)}`, 120000);
|
|
58985
59535
|
scpFromRemote(sshAddress, remotePath, localPath);
|
|
58986
|
-
return JSON.parse(
|
|
59536
|
+
return JSON.parse(readFileSync18(localPath, "utf-8"));
|
|
58987
59537
|
} finally {
|
|
58988
59538
|
try {
|
|
58989
59539
|
runSsh(sshAddress, `rm -f ${shellQuote(remotePath)}`, 1e4);
|
|
@@ -58994,8 +59544,8 @@ function readRemoteBridgeBundle(sshAddress) {
|
|
|
58994
59544
|
}
|
|
58995
59545
|
}
|
|
58996
59546
|
function writeLocalBridgeBundle() {
|
|
58997
|
-
const localPath =
|
|
58998
|
-
|
|
59547
|
+
const localPath = join22(tmpdir4(), `todos-bridge-push-${uuid()}.json`);
|
|
59548
|
+
writeFileSync11(localPath, JSON.stringify(createLocalBridgeBundle(), null, 2));
|
|
58999
59549
|
return localPath;
|
|
59000
59550
|
}
|
|
59001
59551
|
function pushLocalBridgeBundle(sshAddress, dryRun) {
|
|
@@ -60058,7 +60608,7 @@ __export(exports_onboarding_commands, {
|
|
|
60058
60608
|
registerOnboardingCommands: () => registerOnboardingCommands
|
|
60059
60609
|
});
|
|
60060
60610
|
import chalk17 from "chalk";
|
|
60061
|
-
import { resolve as
|
|
60611
|
+
import { resolve as resolve21 } from "path";
|
|
60062
60612
|
function registerOnboardingCommands(program2) {
|
|
60063
60613
|
program2.command("onboarding").alias("demo-fixtures").description("List, show, write, or import bundled local onboarding fixtures").option("--show <name>", "Show one fixture bridge bundle as JSON").option("--write <dir>", "Write all bundled fixture bridge bundles to a directory").option("--import <name>", "Dry-run or apply an onboarding fixture import").option("--apply", "Apply an onboarding fixture import. Defaults to dry-run.").option("--resolve-conflicts", "Safely merge existing local tasks while preserving divergent fields").action(async (opts) => {
|
|
60064
60614
|
const globalOpts = program2.opts();
|
|
@@ -60074,7 +60624,7 @@ function registerOnboardingCommands(program2) {
|
|
|
60074
60624
|
return;
|
|
60075
60625
|
}
|
|
60076
60626
|
if (opts.write) {
|
|
60077
|
-
const result = writeOnboardingFixtureFiles2(
|
|
60627
|
+
const result = writeOnboardingFixtureFiles2(resolve21(opts.write));
|
|
60078
60628
|
if (globalOpts.json) {
|
|
60079
60629
|
output(result, true);
|
|
60080
60630
|
return;
|
|
@@ -63522,8 +64072,8 @@ __export(exports_sdk_integration_fixtures, {
|
|
|
63522
64072
|
TODOS_SDK_INTEGRATION_FIXTURE_SCHEMA_VERSION: () => TODOS_SDK_INTEGRATION_FIXTURE_SCHEMA_VERSION,
|
|
63523
64073
|
TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT: () => TODOS_SDK_INTEGRATION_FIXTURE_GENERATED_AT
|
|
63524
64074
|
});
|
|
63525
|
-
import { mkdirSync as
|
|
63526
|
-
import { join as
|
|
64075
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "fs";
|
|
64076
|
+
import { join as join23 } from "path";
|
|
63527
64077
|
function source5(version) {
|
|
63528
64078
|
return {
|
|
63529
64079
|
packageName: "@hasna/todos",
|
|
@@ -63619,7 +64169,7 @@ function createSdkIntegrationFixturePack(options = {}) {
|
|
|
63619
64169
|
};
|
|
63620
64170
|
}
|
|
63621
64171
|
function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
63622
|
-
|
|
64172
|
+
mkdirSync12(directory, { recursive: true });
|
|
63623
64173
|
const pack = createSdkIntegrationFixturePack(options);
|
|
63624
64174
|
const bundle = getOnboardingFixtureBundle("agent-project-demo");
|
|
63625
64175
|
const files = [
|
|
@@ -63630,8 +64180,8 @@ function writeSdkIntegrationFixtures(directory, options = {}) {
|
|
|
63630
64180
|
];
|
|
63631
64181
|
const written = [];
|
|
63632
64182
|
for (const [name, payload] of files) {
|
|
63633
|
-
const file =
|
|
63634
|
-
|
|
64183
|
+
const file = join23(directory, name);
|
|
64184
|
+
writeFileSync12(file, `${JSON.stringify(payload, null, 2)}
|
|
63635
64185
|
`, "utf-8");
|
|
63636
64186
|
written.push(file);
|
|
63637
64187
|
}
|
|
@@ -63654,7 +64204,7 @@ __export(exports_sdk_fixture_commands, {
|
|
|
63654
64204
|
registerSdkFixtureCommands: () => registerSdkFixtureCommands
|
|
63655
64205
|
});
|
|
63656
64206
|
import chalk19 from "chalk";
|
|
63657
|
-
import { resolve as
|
|
64207
|
+
import { resolve as resolve22 } from "path";
|
|
63658
64208
|
function registerSdkFixtureCommands(program2) {
|
|
63659
64209
|
program2.command("sdk-fixtures").description("List, show, or write local SDK integration fixtures").option("--show", "Print the full fixture pack JSON").option("--write <dir>", "Write fixture pack, bridge fixture, contract snapshots, and example index to a directory").action(async (opts) => {
|
|
63660
64210
|
const globalOpts = program2.opts();
|
|
@@ -63665,7 +64215,7 @@ function registerSdkFixtureCommands(program2) {
|
|
|
63665
64215
|
writeSdkIntegrationFixtures: writeSdkIntegrationFixtures2
|
|
63666
64216
|
} = await Promise.resolve().then(() => (init_sdk_integration_fixtures(), exports_sdk_integration_fixtures));
|
|
63667
64217
|
if (opts.write) {
|
|
63668
|
-
const result = writeSdkIntegrationFixtures2(
|
|
64218
|
+
const result = writeSdkIntegrationFixtures2(resolve22(opts.write));
|
|
63669
64219
|
if (globalOpts.json) {
|
|
63670
64220
|
console.log(JSON.stringify(result));
|
|
63671
64221
|
return;
|
|
@@ -63895,7 +64445,7 @@ var exports_roadmap_commands = {};
|
|
|
63895
64445
|
__export(exports_roadmap_commands, {
|
|
63896
64446
|
registerRoadmapCommands: () => registerRoadmapCommands
|
|
63897
64447
|
});
|
|
63898
|
-
import { readFileSync as
|
|
64448
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "fs";
|
|
63899
64449
|
import chalk21 from "chalk";
|
|
63900
64450
|
function splitList3(value) {
|
|
63901
64451
|
return value?.split(";").flatMap((part) => part.split(",")).map((item) => item.trim()).filter(Boolean);
|
|
@@ -64106,7 +64656,7 @@ function registerRoadmapCommands(program2) {
|
|
|
64106
64656
|
const { exportRoadmapBundle: exportRoadmapBundle2, renderRoadmapMarkdown: renderRoadmapMarkdown2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
64107
64657
|
const content = opts.format === "markdown" ? renderRoadmapMarkdown2(roadmap) : JSON.stringify(exportRoadmapBundle2(roadmap), null, 2);
|
|
64108
64658
|
if (opts.out) {
|
|
64109
|
-
|
|
64659
|
+
writeFileSync13(opts.out, content);
|
|
64110
64660
|
if (!globalOpts.json)
|
|
64111
64661
|
console.log(chalk21.green(`Wrote roadmap export to ${opts.out}`));
|
|
64112
64662
|
}
|
|
@@ -64124,7 +64674,7 @@ function registerRoadmapCommands(program2) {
|
|
|
64124
64674
|
const globalOpts = globalOptions(program2);
|
|
64125
64675
|
try {
|
|
64126
64676
|
const { importRoadmapBundle: importRoadmapBundle2 } = await Promise.resolve().then(() => (init_roadmaps(), exports_roadmaps));
|
|
64127
|
-
const bundle = JSON.parse(
|
|
64677
|
+
const bundle = JSON.parse(readFileSync19(path, "utf8"));
|
|
64128
64678
|
const result = importRoadmapBundle2(bundle, { apply: Boolean(opts.apply) });
|
|
64129
64679
|
if (globalOpts.json) {
|
|
64130
64680
|
output(result, true);
|
|
@@ -64487,7 +65037,7 @@ __export(exports_local_backup_commands, {
|
|
|
64487
65037
|
registerLocalBackupCommands: () => registerLocalBackupCommands
|
|
64488
65038
|
});
|
|
64489
65039
|
import chalk26 from "chalk";
|
|
64490
|
-
import { resolve as
|
|
65040
|
+
import { resolve as resolve23 } from "path";
|
|
64491
65041
|
function globalOptions6(program2) {
|
|
64492
65042
|
const command = program2;
|
|
64493
65043
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
@@ -64509,10 +65059,10 @@ function registerLocalBackupCommands(program2) {
|
|
|
64509
65059
|
const projectId = opts.projectId ?? autoProject(globalOpts);
|
|
64510
65060
|
const backupBundle = createLocalBackup2({
|
|
64511
65061
|
project_id: projectId,
|
|
64512
|
-
output_path: opts.output ?
|
|
65062
|
+
output_path: opts.output ? resolve23(opts.output) : undefined
|
|
64513
65063
|
});
|
|
64514
65064
|
const result = {
|
|
64515
|
-
output_path: opts.output ?
|
|
65065
|
+
output_path: opts.output ? resolve23(opts.output) : null,
|
|
64516
65066
|
backup: backupBundle
|
|
64517
65067
|
};
|
|
64518
65068
|
if (opts.json || globalOpts.json) {
|
|
@@ -65922,9 +66472,17 @@ async function updateProject2(id, input, store) {
|
|
|
65922
66472
|
}
|
|
65923
66473
|
async function createPlan2(input, store, context) {
|
|
65924
66474
|
const timestamp3 = new Date().toISOString();
|
|
66475
|
+
const projectId = input.project_id ?? context?.projectId ?? null;
|
|
66476
|
+
const slug = await resolvePostgresPlanSlug({
|
|
66477
|
+
name: input.name,
|
|
66478
|
+
slug: input.slug,
|
|
66479
|
+
projectId,
|
|
66480
|
+
store
|
|
66481
|
+
});
|
|
65925
66482
|
return store.upsert("plans", {
|
|
65926
66483
|
id: randomUUID3(),
|
|
65927
|
-
|
|
66484
|
+
slug,
|
|
66485
|
+
project_id: projectId,
|
|
65928
66486
|
task_list_id: input.task_list_id ?? context?.taskListId ?? null,
|
|
65929
66487
|
agent_id: input.agent_id ?? context?.agentId ?? null,
|
|
65930
66488
|
name: input.name,
|
|
@@ -65938,7 +66496,17 @@ async function createPlan2(input, store, context) {
|
|
|
65938
66496
|
}
|
|
65939
66497
|
async function updatePlan2(id, input, store) {
|
|
65940
66498
|
const plan = await requireRecord("plans", id, store);
|
|
65941
|
-
|
|
66499
|
+
const patch = definedPatch(input);
|
|
66500
|
+
if (input.slug !== undefined) {
|
|
66501
|
+
patch.slug = await resolvePostgresPlanSlug({
|
|
66502
|
+
name: plan.name,
|
|
66503
|
+
slug: input.slug,
|
|
66504
|
+
projectId: plan.project_id,
|
|
66505
|
+
store,
|
|
66506
|
+
excludeId: id
|
|
66507
|
+
});
|
|
66508
|
+
}
|
|
66509
|
+
return store.upsert("plans", { ...plan, ...patch, updated_at: new Date().toISOString() });
|
|
65942
66510
|
}
|
|
65943
66511
|
async function registerAgent2(input, store, context) {
|
|
65944
66512
|
const existing = (await store.list("agents")).find((agent2) => agent2.name === input.name && agent2.status !== "archived");
|
|
@@ -66191,8 +66759,38 @@ function matchesOne(value, expected) {
|
|
|
66191
66759
|
function priorityRank2(priority) {
|
|
66192
66760
|
return { critical: 0, high: 1, medium: 2, low: 3 }[priority];
|
|
66193
66761
|
}
|
|
66762
|
+
function slugifyRaw(value) {
|
|
66763
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
66764
|
+
}
|
|
66194
66765
|
function slugify2(value) {
|
|
66195
|
-
return value
|
|
66766
|
+
return slugifyRaw(value) || "todos";
|
|
66767
|
+
}
|
|
66768
|
+
function normalizePlanSlug2(value) {
|
|
66769
|
+
const slug = slugifyRaw(value);
|
|
66770
|
+
if (!slug)
|
|
66771
|
+
throw new Error("Invalid plan slug");
|
|
66772
|
+
return slug;
|
|
66773
|
+
}
|
|
66774
|
+
function planSlugBase4(value) {
|
|
66775
|
+
return slugifyRaw(value) || "plan";
|
|
66776
|
+
}
|
|
66777
|
+
async function resolvePostgresPlanSlug(options) {
|
|
66778
|
+
const plans = await options.store.list("plans");
|
|
66779
|
+
const used = new Set(plans.filter((plan) => plan.project_id === options.projectId && plan.id !== options.excludeId && plan.slug).map((plan) => plan.slug));
|
|
66780
|
+
if (options.slug !== undefined) {
|
|
66781
|
+
const slug = normalizePlanSlug2(options.slug);
|
|
66782
|
+
if (used.has(slug))
|
|
66783
|
+
throw new Error(`Plan slug already exists in this scope: ${slug}`);
|
|
66784
|
+
return slug;
|
|
66785
|
+
}
|
|
66786
|
+
const base = planSlugBase4(options.name);
|
|
66787
|
+
let candidate = base;
|
|
66788
|
+
let suffix = 2;
|
|
66789
|
+
while (used.has(candidate)) {
|
|
66790
|
+
candidate = `${base}-${suffix}`;
|
|
66791
|
+
suffix += 1;
|
|
66792
|
+
}
|
|
66793
|
+
return candidate;
|
|
66196
66794
|
}
|
|
66197
66795
|
function definedPatch(value) {
|
|
66198
66796
|
return Object.fromEntries(Object.entries(value).filter(([, entry2]) => entry2 !== undefined));
|