@cardor/agent-harness-kit 1.2.5 → 1.3.0

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.js CHANGED
@@ -687,19 +687,19 @@ async function runBuild(cwd2, opts) {
687
687
  }
688
688
  }
689
689
  async function buildOnce(cwd2) {
690
- const spinner5 = p.spinner();
691
- spinner5.start("Loading config...");
690
+ const spinner6 = p.spinner();
691
+ spinner6.start("Loading config...");
692
692
  try {
693
693
  const config = await loadConfig(cwd2);
694
- spinner5.message("Rebuilding files...");
694
+ spinner6.message("Rebuilding files...");
695
695
  const materializer = getMaterializer(config.provider);
696
696
  await materializer.build(config, cwd2);
697
- spinner5.stop(pc.green("Build complete"));
697
+ spinner6.stop(pc.green("Build complete"));
698
698
  p.log.success("AGENTS.md");
699
699
  p.log.success(`Agent definitions (${config.provider})`);
700
700
  p.log.success("MCP config");
701
701
  } catch (err) {
702
- spinner5.stop(pc.red("Build failed"));
702
+ spinner6.stop(pc.red("Build failed"));
703
703
  p.log.error(err instanceof Error ? err.message : String(err));
704
704
  process.exit(1);
705
705
  }
@@ -757,7 +757,8 @@ function startDashboardServer(db, dbPath, staticPath, port) {
757
757
  });
758
758
  app.get("/api/tasks", async (c) => {
759
759
  await db.reconnect();
760
- return c.json(await tasks.getAllWithAcceptanceCounts());
760
+ const includeArchived = c.req.query("includeArchived") === "true";
761
+ return c.json(await tasks.getAllWithAcceptanceCounts(includeArchived));
761
762
  });
762
763
  app.get("/api/tasks/:id", async (c) => {
763
764
  await db.reconnect();
@@ -768,6 +769,39 @@ function startDashboardServer(db, dbPath, staticPath, port) {
768
769
  const taskActions = await actions.getWithDetails(id);
769
770
  return c.json({ ...task2, acceptance, actions: taskActions });
770
771
  });
772
+ app.patch("/api/tasks/:id", async (c) => {
773
+ const id = parseInt(c.req.param("id"));
774
+ const task2 = await tasks.getById(id);
775
+ if (!task2) return c.json({ error: "Not found" }, 404);
776
+ const body = await c.req.json();
777
+ const updateParams = {};
778
+ if (body.title !== void 0) updateParams.title = body.title;
779
+ if (body.description !== void 0) updateParams.description = body.description;
780
+ await db.updateTask(id, updateParams);
781
+ if (body.acceptance !== void 0 && Array.isArray(body.acceptance)) {
782
+ await db.updateTaskAcceptance(id, body.acceptance.map((a) => a.trim()).filter(Boolean));
783
+ }
784
+ const updated = await tasks.getById(id);
785
+ const acceptance = await tasks.getAcceptance(id);
786
+ const taskActions = await actions.getWithDetails(id);
787
+ return c.json({ ...updated, acceptance, actions: taskActions });
788
+ });
789
+ app.patch("/api/tasks/:id/archive", async (c) => {
790
+ await db.reconnect();
791
+ const id = parseInt(c.req.param("id"));
792
+ const task2 = await tasks.getById(id);
793
+ if (!task2) return c.json({ error: "Not found" }, 404);
794
+ const updated = await db.archiveTask(id);
795
+ return c.json(updated);
796
+ });
797
+ app.patch("/api/tasks/:id/unarchive", async (c) => {
798
+ await db.reconnect();
799
+ const id = parseInt(c.req.param("id"));
800
+ const task2 = await tasks.getById(id);
801
+ if (!task2) return c.json({ error: "Not found" }, 404);
802
+ const updated = await db.unarchiveTask(id);
803
+ return c.json(updated);
804
+ });
771
805
  app.get("/api/tools/top", async (c) => {
772
806
  await db.reconnect();
773
807
  const limit = parseInt(c.req.query("limit") ?? "20");
@@ -1074,22 +1108,36 @@ var TaskRepository = class {
1074
1108
  );
1075
1109
  }
1076
1110
  }
1077
- async getAll(status) {
1111
+ async getAll(status, includeArchived = false) {
1112
+ let sql = `SELECT * FROM tasks`;
1113
+ const params = [];
1114
+ const conditions = [];
1115
+ if (!includeArchived) {
1116
+ conditions.push(`archived_at IS NULL`);
1117
+ }
1078
1118
  if (status) {
1079
- return this.driver.query(`SELECT * FROM tasks WHERE status = ? ORDER BY id`, [status]);
1119
+ conditions.push(`status = ?`);
1120
+ params.push(status);
1121
+ }
1122
+ if (conditions.length > 0) {
1123
+ sql += ` WHERE ${conditions.join(" AND ")}`;
1080
1124
  }
1081
- return this.driver.query(`SELECT * FROM tasks ORDER BY id`);
1125
+ sql += ` ORDER BY id`;
1126
+ return this.driver.query(sql, params);
1082
1127
  }
1083
- async getAllWithAcceptanceCounts() {
1084
- return this.driver.query(`
1128
+ async getAllWithAcceptanceCounts(includeArchived = false) {
1129
+ let sql = `
1085
1130
  SELECT t.*,
1086
1131
  COUNT(ta.id) as acceptance_total,
1087
1132
  COALESCE(SUM(ta.met), 0) as acceptance_met
1088
1133
  FROM tasks t
1089
1134
  LEFT JOIN task_acceptance ta ON ta.task_id = t.id
1090
- GROUP BY t.id
1091
- ORDER BY t.id
1092
- `);
1135
+ `;
1136
+ if (!includeArchived) {
1137
+ sql += ` WHERE t.archived_at IS NULL`;
1138
+ }
1139
+ sql += ` GROUP BY t.id ORDER BY t.id`;
1140
+ return this.driver.query(sql);
1093
1141
  }
1094
1142
  async getById(id) {
1095
1143
  return this.driver.queryOne(`SELECT * FROM tasks WHERE id = ?`, [id]);
@@ -1118,6 +1166,46 @@ var TaskRepository = class {
1118
1166
  await this.driver.exec(`UPDATE tasks SET status = ? WHERE id = ?`, [status, id]);
1119
1167
  }
1120
1168
  }
1169
+ async update(id, params) {
1170
+ const sets = [];
1171
+ const vals = [];
1172
+ if (params.title !== void 0) {
1173
+ sets.push("title = ?");
1174
+ vals.push(params.title);
1175
+ }
1176
+ if (params.description !== void 0) {
1177
+ sets.push("description = ?");
1178
+ vals.push(params.description);
1179
+ }
1180
+ if (params.slug !== void 0) {
1181
+ sets.push("slug = ?");
1182
+ vals.push(params.slug);
1183
+ }
1184
+ if (sets.length === 0) return;
1185
+ vals.push(id);
1186
+ await this.driver.exec(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, vals);
1187
+ }
1188
+ async replaceAcceptance(taskId, criteria) {
1189
+ await this.driver.exec(`DELETE FROM task_acceptance WHERE task_id = ?`, [taskId]);
1190
+ for (const criterion of criteria) {
1191
+ await this.driver.exec(
1192
+ `INSERT INTO task_acceptance (task_id, criterion) VALUES (?, ?)`,
1193
+ [taskId, criterion]
1194
+ );
1195
+ }
1196
+ }
1197
+ async archive(id) {
1198
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1199
+ await this.driver.exec(`UPDATE tasks SET archived_at = ? WHERE id = ?`, [now, id]);
1200
+ }
1201
+ async unarchive(id) {
1202
+ await this.driver.exec(`UPDATE tasks SET archived_at = NULL WHERE id = ?`, [id]);
1203
+ }
1204
+ async getArchived() {
1205
+ return this.driver.query(
1206
+ `SELECT * FROM tasks WHERE archived_at IS NOT NULL ORDER BY archived_at DESC`
1207
+ );
1208
+ }
1121
1209
  async claim(id, agent, now) {
1122
1210
  return this.driver.exec(
1123
1211
  `UPDATE tasks SET status = 'in_progress', assigned_to = ?, started_at = ? WHERE id = ? AND status = 'pending'`,
@@ -1129,7 +1217,7 @@ var TaskRepository = class {
1129
1217
  }
1130
1218
  async getStatusSummary() {
1131
1219
  return this.driver.query(
1132
- `SELECT status, COUNT(*) as total FROM tasks GROUP BY status`
1220
+ `SELECT status, COUNT(*) as total FROM tasks WHERE archived_at IS NULL GROUP BY status`
1133
1221
  );
1134
1222
  }
1135
1223
  };
@@ -1161,8 +1249,8 @@ var HarnessDB = class {
1161
1249
  await this.regenerateCurrentMd();
1162
1250
  return await this.tasks.getById(taskId);
1163
1251
  }
1164
- async getTasks(status) {
1165
- return this.tasks.getAll(status);
1252
+ async getTasks(status, includeArchived = false) {
1253
+ return this.tasks.getAll(status, includeArchived);
1166
1254
  }
1167
1255
  async getTaskById(id) {
1168
1256
  return this.tasks.getById(id);
@@ -1202,6 +1290,28 @@ var HarnessDB = class {
1202
1290
  async markAcceptanceMet(criterionId) {
1203
1291
  return this.tasks.markAcceptanceMet(criterionId);
1204
1292
  }
1293
+ async updateTask(id, params) {
1294
+ await this.tasks.update(id, params);
1295
+ await this.regenerateCurrentMd();
1296
+ return await this.tasks.getById(id);
1297
+ }
1298
+ async updateTaskAcceptance(taskId, criteria) {
1299
+ await this.tasks.replaceAcceptance(taskId, criteria);
1300
+ await this.regenerateCurrentMd();
1301
+ }
1302
+ async archiveTask(id) {
1303
+ await this.tasks.archive(id);
1304
+ await this.regenerateCurrentMd();
1305
+ return await this.tasks.getById(id);
1306
+ }
1307
+ async unarchiveTask(id) {
1308
+ await this.tasks.unarchive(id);
1309
+ await this.regenerateCurrentMd();
1310
+ return await this.tasks.getById(id);
1311
+ }
1312
+ async getArchivedTasks() {
1313
+ return this.tasks.getArchived();
1314
+ }
1205
1315
  async getStatusSummary() {
1206
1316
  return this.tasks.getStatusSummary();
1207
1317
  }
@@ -1330,7 +1440,7 @@ var HarnessDB = class {
1330
1440
  // ─── Export helpers ───────────────────────────────────────────────────────
1331
1441
  async exportJson() {
1332
1442
  return {
1333
- tasks: await this.tasks.getAll(),
1443
+ tasks: await this.tasks.getAll(void 0, true),
1334
1444
  actions: await this.actions.getAll(),
1335
1445
  sections: await this.actions.getAllSections()
1336
1446
  };
@@ -1356,7 +1466,7 @@ var HarnessDB = class {
1356
1466
  return { added, skipped };
1357
1467
  }
1358
1468
  async writeFeatureList(cwd2) {
1359
- const allTasks = await this.tasks.getAll();
1469
+ const allTasks = await this.tasks.getAll(void 0, true);
1360
1470
  const list = await Promise.all(
1361
1471
  allTasks.map(async (t) => ({
1362
1472
  slug: t.slug,
@@ -1375,13 +1485,13 @@ async function openDB(config, cwd2) {
1375
1485
  const dbConfig = config.database;
1376
1486
  let driver;
1377
1487
  if (dbConfig.type === "postgres") {
1378
- const { PostgresDriver } = await import("./postgres-KQMJNRS2.js");
1488
+ const { PostgresDriver } = await import("./postgres-6BXN7ZH4.js");
1379
1489
  driver = new PostgresDriver(dbConfig);
1380
1490
  } else if (dbConfig.type === "mysql") {
1381
- const { MySQLDriver } = await import("./mysql-LQXFOAQE.js");
1491
+ const { MySQLDriver } = await import("./mysql-NXLYFD2H.js");
1382
1492
  driver = new MySQLDriver(dbConfig);
1383
1493
  } else {
1384
- const { SQLiteDriver } = await import("./sqlite-NXFRDHK6.js");
1494
+ const { SQLiteDriver } = await import("./sqlite-M65L55DA.js");
1385
1495
  if (dbConfig.type !== "sqlite") {
1386
1496
  throw new Error("Invalid database type");
1387
1497
  }
@@ -1814,8 +1924,8 @@ async function runInit(cwd2, flags) {
1814
1924
  }
1815
1925
  firstTask = { title: taskTitle, description: taskDesc, acceptance };
1816
1926
  }
1817
- const spinner5 = p3.spinner();
1818
- spinner5.start("Scaffolding...");
1927
+ const spinner6 = p3.spinner();
1928
+ spinner6.start("Scaffolding...");
1819
1929
  try {
1820
1930
  const config = applyConfigDefaults({ name, description, provider, docsPath, tasksAdapter });
1821
1931
  const materializer = getMaterializer(provider);
@@ -1849,9 +1959,9 @@ async function runInit(cwd2, flags) {
1849
1959
  });
1850
1960
  }
1851
1961
  await db.close();
1852
- spinner5.stop("");
1962
+ spinner6.stop("");
1853
1963
  } catch (err) {
1854
- spinner5.stop("Failed");
1964
+ spinner6.stop("Failed");
1855
1965
  p3.log.error(err instanceof Error ? err.message : String(err));
1856
1966
  throw err;
1857
1967
  }
@@ -1912,16 +2022,16 @@ async function runMigrate(cwd2, opts) {
1912
2022
  console.log(pc7.dim(`Already on ${target} \u2014 nothing to migrate.`));
1913
2023
  return;
1914
2024
  }
1915
- const spinner5 = p4.spinner();
1916
- spinner5.start(`Migrating from ${config.provider} to ${target}...`);
2025
+ const spinner6 = p4.spinner();
2026
+ spinner6.start(`Migrating from ${config.provider} to ${target}...`);
1917
2027
  try {
1918
2028
  const targetMaterializer = getMaterializer(target);
1919
2029
  await targetMaterializer.build(config, cwd2);
1920
- spinner5.stop(pc7.green(`Migrated to ${target}`));
2030
+ spinner6.stop(pc7.green(`Migrated to ${target}`));
1921
2031
  p4.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
1922
2032
  p4.log.warn(`Then run: ahk build`);
1923
2033
  } catch (err) {
1924
- spinner5.stop(pc7.red("Migration failed"));
2034
+ spinner6.stop(pc7.red("Migration failed"));
1925
2035
  p4.log.error(err instanceof Error ? err.message : String(err));
1926
2036
  process.exit(1);
1927
2037
  }
@@ -2129,7 +2239,7 @@ var TOOLS = [
2129
2239
  },
2130
2240
  {
2131
2241
  name: "tasks.get",
2132
- description: "List tasks, optionally filtered by status.",
2242
+ description: "List tasks, optionally filtered by status. Excludes archived tasks by default.",
2133
2243
  inputSchema: {
2134
2244
  type: "object",
2135
2245
  properties: {
@@ -2137,6 +2247,10 @@ var TOOLS = [
2137
2247
  type: "string",
2138
2248
  enum: ["pending", "in_progress", "done", "blocked"],
2139
2249
  description: "Filter by status (omit for all tasks)"
2250
+ },
2251
+ includeArchived: {
2252
+ type: "boolean",
2253
+ description: "If true, include archived tasks in results"
2140
2254
  }
2141
2255
  }
2142
2256
  }
@@ -2239,6 +2353,46 @@ var TOOLS = [
2239
2353
  },
2240
2354
  required: ["actionId", "toolName"]
2241
2355
  }
2356
+ },
2357
+ {
2358
+ name: "tasks.edit",
2359
+ description: "Edit an existing task (title, description, acceptance criteria). Omitted fields keep their current values.",
2360
+ inputSchema: {
2361
+ type: "object",
2362
+ properties: {
2363
+ id: { type: "number", description: "Task ID to edit" },
2364
+ title: { type: "string", description: "New title (optional)" },
2365
+ description: { type: "string", description: "New description (optional, null to clear)" },
2366
+ acceptance: {
2367
+ type: "array",
2368
+ items: { type: "string" },
2369
+ description: "New acceptance criteria list (optional, null to keep existing)"
2370
+ }
2371
+ },
2372
+ required: ["id"]
2373
+ }
2374
+ },
2375
+ {
2376
+ name: "tasks.archive",
2377
+ description: "Archive a task. Archived tasks are hidden from default views (CLI and dashboard).",
2378
+ inputSchema: {
2379
+ type: "object",
2380
+ properties: {
2381
+ id: { type: "number", description: "Task ID to archive" }
2382
+ },
2383
+ required: ["id"]
2384
+ }
2385
+ },
2386
+ {
2387
+ name: "tasks.unarchive",
2388
+ description: "Unarchive a previously archived task, restoring it to default views.",
2389
+ inputSchema: {
2390
+ type: "object",
2391
+ properties: {
2392
+ id: { type: "number", description: "Task ID to unarchive" }
2393
+ },
2394
+ required: ["id"]
2395
+ }
2242
2396
  }
2243
2397
  ];
2244
2398
  async function startMcpServer(config, cwd2) {
@@ -2296,7 +2450,8 @@ async function dispatch(name, args, db, docsPath) {
2296
2450
  }
2297
2451
  case "tasks.get": {
2298
2452
  const status = args["status"];
2299
- const tasks = status ? await db.getTasks(status) : await db.getTasks();
2453
+ const includeArchived = args["includeArchived"];
2454
+ const tasks = status ? await db.getTasks(status, includeArchived ?? false) : await db.getTasks(void 0, includeArchived ?? false);
2300
2455
  return ok(JSON.stringify(tasks, null, 2));
2301
2456
  }
2302
2457
  case "tasks.claim": {
@@ -2351,6 +2506,30 @@ async function dispatch(name, args, db, docsPath) {
2351
2506
  await db.recordTool(actionId, toolName, argsJson, resultSummary);
2352
2507
  return ok(JSON.stringify({ actionId, toolName, recorded: true }));
2353
2508
  }
2509
+ case "tasks.edit": {
2510
+ const id = num(args, "id");
2511
+ const title = args["title"];
2512
+ const description = args["description"];
2513
+ const acceptance = args["acceptance"];
2514
+ const task2 = await db.getTaskById(id);
2515
+ if (!task2) return ok(JSON.stringify({ error: "Task not found", taskId: id }), true);
2516
+ await db.updateTask(id, { title, description: description !== void 0 ? description : void 0 });
2517
+ if (acceptance !== void 0 && acceptance !== null) {
2518
+ await db.updateTaskAcceptance(id, acceptance);
2519
+ }
2520
+ const updated = await db.getTaskById(id);
2521
+ return ok(JSON.stringify(updated));
2522
+ }
2523
+ case "tasks.archive": {
2524
+ const id = num(args, "id");
2525
+ const task2 = await db.archiveTask(id);
2526
+ return ok(JSON.stringify(task2));
2527
+ }
2528
+ case "tasks.unarchive": {
2529
+ const id = num(args, "id");
2530
+ const task2 = await db.unarchiveTask(id);
2531
+ return ok(JSON.stringify(task2));
2532
+ }
2354
2533
  default:
2355
2534
  return ok(`Unknown tool: ${name}`, true);
2356
2535
  }
@@ -2396,8 +2575,8 @@ function collectMarkdownFiles(dir) {
2396
2575
  }
2397
2576
  return files;
2398
2577
  }
2399
- function ok(text3, isError = false) {
2400
- return { content: [{ type: "text", text: text3 }], isError };
2578
+ function ok(text4, isError = false) {
2579
+ return { content: [{ type: "text", text: text4 }], isError };
2401
2580
  }
2402
2581
  function str(args, key) {
2403
2582
  const v4 = args[key];
@@ -2444,7 +2623,8 @@ async function runStatus(cwd2, opts) {
2444
2623
  acceptance: await db.getTaskAcceptance(t.id)
2445
2624
  }))
2446
2625
  );
2447
- console.log(JSON.stringify({ tasks: actions, summary }, null, 2));
2626
+ const archivedCount = (await db.getArchivedTasks()).length;
2627
+ console.log(JSON.stringify({ tasks: actions, summary, archivedCount }, null, 2));
2448
2628
  return;
2449
2629
  }
2450
2630
  if (tasks.length === 0) {
@@ -2485,6 +2665,10 @@ async function runStatus(cwd2, opts) {
2485
2665
  return `${fn(s.status)}: ${s.total}`;
2486
2666
  });
2487
2667
  console.log(pc9.dim("Tasks \u2014 ") + parts.join(pc9.dim(" | ")));
2668
+ const archivedTasks = await db.getArchivedTasks();
2669
+ if (archivedTasks.length > 0) {
2670
+ console.log(pc9.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
2671
+ }
2488
2672
  } finally {
2489
2673
  await db.close();
2490
2674
  }
@@ -2581,8 +2765,8 @@ async function runTaskAdd(cwd2) {
2581
2765
  if (p6.isCancel(val) || !val || !val.trim()) break;
2582
2766
  acceptance.push(val.trim());
2583
2767
  }
2584
- const spinner5 = p6.spinner();
2585
- spinner5.start("Saving...");
2768
+ const spinner6 = p6.spinner();
2769
+ spinner6.start("Saving...");
2586
2770
  try {
2587
2771
  const config = await loadConfig(cwd2);
2588
2772
  const db = await openDB(config, cwd2);
@@ -2590,11 +2774,11 @@ async function runTaskAdd(cwd2) {
2590
2774
  const task2 = await db.addTask({ slug, title, description: description || void 0, acceptance });
2591
2775
  await db.writeFeatureList(cwd2);
2592
2776
  await db.close();
2593
- spinner5.stop("");
2777
+ spinner6.stop("");
2594
2778
  console.log(pc11.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
2595
2779
  console.log(pc11.cyan("\u2192") + " " + pc11.cyan("ahk status") + " to see all tasks");
2596
2780
  } catch (err) {
2597
- spinner5.stop(pc11.red("Failed"));
2781
+ spinner6.stop(pc11.red("Failed"));
2598
2782
  p6.log.error(err instanceof Error ? err.message : String(err));
2599
2783
  process.exit(1);
2600
2784
  }
@@ -2640,14 +2824,107 @@ async function runTaskDone(cwd2, idOrSlug) {
2640
2824
  }
2641
2825
  }
2642
2826
 
2827
+ // src/commands/task/edit.ts
2828
+ import * as p7 from "@clack/prompts";
2829
+ import pc13 from "picocolors";
2830
+ async function runTaskEdit(cwd2) {
2831
+ p7.intro(pc13.bold("agent-harness-kit \u2014 edit task"));
2832
+ const config = await loadConfig(cwd2);
2833
+ const db = await openDB(config, cwd2);
2834
+ try {
2835
+ const allTasks = await db.getTasks();
2836
+ const activeTasks = allTasks.filter((t) => t.status !== "done");
2837
+ if (activeTasks.length === 0) {
2838
+ p7.log.error("No active tasks to edit.");
2839
+ return;
2840
+ }
2841
+ const taskId = await p7.select({
2842
+ message: "Select a task to edit",
2843
+ options: activeTasks.map((t) => ({
2844
+ label: `#${t.id} \u2014 ${t.title} (${t.slug})`,
2845
+ value: t.id
2846
+ }))
2847
+ });
2848
+ if (p7.isCancel(taskId)) {
2849
+ p7.cancel("Cancelled.");
2850
+ process.exit(0);
2851
+ }
2852
+ const task2 = await db.getTaskById(taskId);
2853
+ if (!task2) {
2854
+ p7.log.error("Task not found");
2855
+ process.exit(1);
2856
+ }
2857
+ const title = await p7.text({
2858
+ message: "Title",
2859
+ initialValue: task2.title
2860
+ });
2861
+ if (p7.isCancel(title)) {
2862
+ p7.cancel("Cancelled.");
2863
+ process.exit(0);
2864
+ }
2865
+ const description = await p7.text({
2866
+ message: "Description (what and why)",
2867
+ initialValue: task2.description ?? ""
2868
+ });
2869
+ if (p7.isCancel(description)) {
2870
+ p7.cancel("Cancelled.");
2871
+ process.exit(0);
2872
+ }
2873
+ const currentAcceptance = await db.getTaskAcceptance(task2.id);
2874
+ const newAcceptance = [];
2875
+ p7.log.info("Acceptance criteria \u2014 edit each, empty to delete. Add new ones at the end.");
2876
+ for (let i = 0; i < currentAcceptance.length; i++) {
2877
+ const ac = currentAcceptance[i];
2878
+ const val = await p7.text({
2879
+ message: `#${i + 1}/${currentAcceptance.length}`,
2880
+ initialValue: ac.criterion,
2881
+ defaultValue: ""
2882
+ });
2883
+ if (p7.isCancel(val)) {
2884
+ p7.cancel("Cancelled.");
2885
+ process.exit(0);
2886
+ }
2887
+ const trimmed = val.trim();
2888
+ if (trimmed !== "") {
2889
+ newAcceptance.push(trimmed);
2890
+ }
2891
+ }
2892
+ while (true) {
2893
+ const val = await p7.text({ message: "New acceptance criterion", placeholder: "(press Enter to finish)" });
2894
+ if (p7.isCancel(val) || !val || !val.trim()) break;
2895
+ newAcceptance.push(val.trim());
2896
+ }
2897
+ const spinner6 = p7.spinner();
2898
+ spinner6.start("Saving...");
2899
+ try {
2900
+ const newSlug = slugify(title);
2901
+ await db.updateTask(task2.id, {
2902
+ title,
2903
+ description: description.trim() || void 0,
2904
+ slug: newSlug
2905
+ });
2906
+ await db.updateTaskAcceptance(task2.id, newAcceptance);
2907
+ await db.writeFeatureList(cwd2);
2908
+ spinner6.stop("");
2909
+ console.log(pc13.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
2910
+ } catch (err) {
2911
+ spinner6.stop(pc13.red("Failed"));
2912
+ p7.log.error(err instanceof Error ? err.message : String(err));
2913
+ process.exit(1);
2914
+ }
2915
+ } finally {
2916
+ await db.close();
2917
+ }
2918
+ }
2919
+
2643
2920
  // src/commands/task/list.ts
2644
2921
  import Table2 from "cli-table3";
2645
- import pc13 from "picocolors";
2922
+ import pc14 from "picocolors";
2646
2923
  var STATUS_COLOR2 = {
2647
- pending: (s) => pc13.dim(s),
2648
- in_progress: (s) => pc13.cyan(s),
2649
- done: (s) => pc13.green(s),
2650
- blocked: (s) => pc13.red(s)
2924
+ pending: (s) => pc14.dim(s),
2925
+ in_progress: (s) => pc14.cyan(s),
2926
+ done: (s) => pc14.green(s),
2927
+ blocked: (s) => pc14.red(s)
2651
2928
  };
2652
2929
  async function runTaskList(cwd2, opts) {
2653
2930
  const config = await loadConfig(cwd2);
@@ -2655,17 +2932,20 @@ async function runTaskList(cwd2, opts) {
2655
2932
  try {
2656
2933
  const validStatuses = ["pending", "in_progress", "done", "blocked"];
2657
2934
  const filterStatus = opts.status && validStatuses.includes(opts.status) ? opts.status : void 0;
2658
- const tasks = filterStatus ? await db.getTasks(filterStatus) : await db.getTasks();
2935
+ const tasks = opts.archived ? await db.getArchivedTasks() : filterStatus ? await db.getTasks(filterStatus, opts.includeArchived ?? false) : await db.getTasks(void 0, opts.includeArchived ?? false);
2659
2936
  if (opts.json) {
2660
2937
  console.log(JSON.stringify(tasks, null, 2));
2661
2938
  return;
2662
2939
  }
2663
2940
  if (tasks.length === 0) {
2664
- console.log(pc13.dim("No tasks" + (filterStatus ? ` with status: ${filterStatus}` : "") + "."));
2941
+ let msg = "No tasks";
2942
+ if (filterStatus) msg += ` with status: ${filterStatus}`;
2943
+ if (opts.archived) msg += " (archived)";
2944
+ console.log(pc14.dim(msg + "."));
2665
2945
  return;
2666
2946
  }
2667
2947
  const table = new Table2({
2668
- head: ["ID", "Slug", "Title", "Status"].map((h) => pc13.bold(h)),
2948
+ head: ["ID", "Slug", "Title", "Status"].map((h) => pc14.bold(h)),
2669
2949
  style: { head: [], border: [] }
2670
2950
  });
2671
2951
  for (const t of tasks) {
@@ -2673,6 +2953,12 @@ async function runTaskList(cwd2, opts) {
2673
2953
  table.push([String(t.id), t.slug, t.title.slice(0, 50), colorFn(t.status)]);
2674
2954
  }
2675
2955
  console.log(table.toString());
2956
+ if (!opts.archived && !opts.includeArchived) {
2957
+ const archivedTasks = await db.getArchivedTasks();
2958
+ if (archivedTasks.length > 0) {
2959
+ console.log(pc14.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
2960
+ }
2961
+ }
2676
2962
  } finally {
2677
2963
  await db.close();
2678
2964
  }
@@ -2687,7 +2973,7 @@ var pkgPath = join16(dirname5(fileURLToPath3(import.meta.url)), "..", "package.j
2687
2973
  var pkg = require2(pkgPath);
2688
2974
 
2689
2975
  // src/core/update-check.ts
2690
- import pc14 from "picocolors";
2976
+ import pc15 from "picocolors";
2691
2977
  var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
2692
2978
  var TIMEOUT_MS = 2500;
2693
2979
  function checkForUpdate(currentVersion) {
@@ -2705,8 +2991,8 @@ function checkForUpdate(currentVersion) {
2705
2991
  }
2706
2992
  function printUpdateMessage({ current, latest }) {
2707
2993
  const lines = [
2708
- ` Update available ${pc14.dim(current)} \u2192 ${pc14.green(latest)} `,
2709
- ` Run: ${pc14.cyan(`pnpm i ${pkg.name}@${latest}`)} `
2994
+ ` Update available ${pc15.dim(current)} \u2192 ${pc15.green(latest)} `,
2995
+ ` Run: ${pc15.cyan(`pnpm i ${pkg.name}@${latest}`)} `
2710
2996
  ];
2711
2997
  drawBox(lines);
2712
2998
  }
@@ -2746,12 +3032,15 @@ var task = program.command("task").description("Manage tasks");
2746
3032
  task.command("add").description("Add a task interactively").action(async () => {
2747
3033
  await runTaskAdd(cwd);
2748
3034
  });
2749
- task.command("list").description("List tasks").option("--status <status>", "Filter by status: pending | in_progress | done | blocked").option("--json", "Output as JSON").action(async (opts) => {
3035
+ task.command("list").description("List tasks").option("--status <status>", "Filter by status: pending | in_progress | done | blocked").option("--archived", "Show only archived tasks").option("--include-archived", "Include archived tasks in the list").option("--json", "Output as JSON").action(async (opts) => {
2750
3036
  await runTaskList(cwd, opts);
2751
3037
  });
2752
3038
  task.command("done <id|slug>").description("Mark a task as done").action(async (idOrSlug) => {
2753
3039
  await runTaskDone(cwd, idOrSlug);
2754
3040
  });
3041
+ task.command("edit").description("Edit a task interactively").action(async () => {
3042
+ await runTaskEdit(cwd);
3043
+ });
2755
3044
  program.command("dashboard").description("Open web dashboard to visualize harness data").option("-p, --port <port>", "Port to listen on", "4242").option("--no-open", "Do not open browser automatically").action(async (opts) => {
2756
3045
  await runDashboard(cwd, { port: parseInt(opts.port), open: opts.open });
2757
3046
  });