@cardor/agent-harness-kit 1.2.4 → 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
  }
@@ -745,6 +745,7 @@ function startDashboardServer(db, dbPath, staticPath, port) {
745
745
  c.res.headers.set("Access-Control-Allow-Origin", "*");
746
746
  });
747
747
  app.get("/api/stats", async (c) => {
748
+ await db.reconnect();
748
749
  const summary = await tasks.getStatusSummary();
749
750
  const byStatus = { pending: 0, in_progress: 0, done: 0, blocked: 0 };
750
751
  for (const { status, total } of summary) byStatus[status] = total;
@@ -755,9 +756,12 @@ function startDashboardServer(db, dbPath, staticPath, port) {
755
756
  return c.json({ ok: true });
756
757
  });
757
758
  app.get("/api/tasks", async (c) => {
758
- return c.json(await tasks.getAllWithAcceptanceCounts());
759
+ await db.reconnect();
760
+ const includeArchived = c.req.query("includeArchived") === "true";
761
+ return c.json(await tasks.getAllWithAcceptanceCounts(includeArchived));
759
762
  });
760
763
  app.get("/api/tasks/:id", async (c) => {
764
+ await db.reconnect();
761
765
  const id = parseInt(c.req.param("id"));
762
766
  const task2 = await tasks.getById(id);
763
767
  if (!task2) return c.json({ error: "Not found" }, 404);
@@ -765,26 +769,65 @@ function startDashboardServer(db, dbPath, staticPath, port) {
765
769
  const taskActions = await actions.getWithDetails(id);
766
770
  return c.json({ ...task2, acceptance, actions: taskActions });
767
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
+ });
768
805
  app.get("/api/tools/top", async (c) => {
806
+ await db.reconnect();
769
807
  const limit = parseInt(c.req.query("limit") ?? "20");
770
808
  return c.json(await actions.getTopTools(limit));
771
809
  });
772
810
  app.get("/api/tools/recent", async (c) => {
811
+ await db.reconnect();
773
812
  const limit = parseInt(c.req.query("limit") ?? "50");
774
813
  return c.json(await stats.getRecentTools(limit));
775
814
  });
776
815
  app.get("/api/files/top", async (c) => {
816
+ await db.reconnect();
777
817
  const limit = parseInt(c.req.query("limit") ?? "20");
778
818
  return c.json(await stats.getTopFiles(limit));
779
819
  });
780
820
  app.get("/api/files/recent", async (c) => {
821
+ await db.reconnect();
781
822
  const limit = parseInt(c.req.query("limit") ?? "50");
782
823
  return c.json(await stats.getRecentFiles(limit));
783
824
  });
784
825
  app.get("/api/agents/stats", async (c) => {
826
+ await db.reconnect();
785
827
  return c.json(await stats.getAgentStats());
786
828
  });
787
829
  app.get("/api/timeline", async (c) => {
830
+ await db.reconnect();
788
831
  const limit = parseInt(c.req.query("limit") ?? "50");
789
832
  return c.json(await stats.getTimeline(limit));
790
833
  });
@@ -1065,22 +1108,36 @@ var TaskRepository = class {
1065
1108
  );
1066
1109
  }
1067
1110
  }
1068
- 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
+ }
1069
1118
  if (status) {
1070
- 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 ")}`;
1071
1124
  }
1072
- return this.driver.query(`SELECT * FROM tasks ORDER BY id`);
1125
+ sql += ` ORDER BY id`;
1126
+ return this.driver.query(sql, params);
1073
1127
  }
1074
- async getAllWithAcceptanceCounts() {
1075
- return this.driver.query(`
1128
+ async getAllWithAcceptanceCounts(includeArchived = false) {
1129
+ let sql = `
1076
1130
  SELECT t.*,
1077
1131
  COUNT(ta.id) as acceptance_total,
1078
1132
  COALESCE(SUM(ta.met), 0) as acceptance_met
1079
1133
  FROM tasks t
1080
1134
  LEFT JOIN task_acceptance ta ON ta.task_id = t.id
1081
- GROUP BY t.id
1082
- ORDER BY t.id
1083
- `);
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);
1084
1141
  }
1085
1142
  async getById(id) {
1086
1143
  return this.driver.queryOne(`SELECT * FROM tasks WHERE id = ?`, [id]);
@@ -1109,6 +1166,46 @@ var TaskRepository = class {
1109
1166
  await this.driver.exec(`UPDATE tasks SET status = ? WHERE id = ?`, [status, id]);
1110
1167
  }
1111
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
+ }
1112
1209
  async claim(id, agent, now) {
1113
1210
  return this.driver.exec(
1114
1211
  `UPDATE tasks SET status = 'in_progress', assigned_to = ?, started_at = ? WHERE id = ? AND status = 'pending'`,
@@ -1120,7 +1217,7 @@ var TaskRepository = class {
1120
1217
  }
1121
1218
  async getStatusSummary() {
1122
1219
  return this.driver.query(
1123
- `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`
1124
1221
  );
1125
1222
  }
1126
1223
  };
@@ -1152,8 +1249,8 @@ var HarnessDB = class {
1152
1249
  await this.regenerateCurrentMd();
1153
1250
  return await this.tasks.getById(taskId);
1154
1251
  }
1155
- async getTasks(status) {
1156
- return this.tasks.getAll(status);
1252
+ async getTasks(status, includeArchived = false) {
1253
+ return this.tasks.getAll(status, includeArchived);
1157
1254
  }
1158
1255
  async getTaskById(id) {
1159
1256
  return this.tasks.getById(id);
@@ -1193,6 +1290,28 @@ var HarnessDB = class {
1193
1290
  async markAcceptanceMet(criterionId) {
1194
1291
  return this.tasks.markAcceptanceMet(criterionId);
1195
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
+ }
1196
1315
  async getStatusSummary() {
1197
1316
  return this.tasks.getStatusSummary();
1198
1317
  }
@@ -1321,11 +1440,14 @@ var HarnessDB = class {
1321
1440
  // ─── Export helpers ───────────────────────────────────────────────────────
1322
1441
  async exportJson() {
1323
1442
  return {
1324
- tasks: await this.tasks.getAll(),
1443
+ tasks: await this.tasks.getAll(void 0, true),
1325
1444
  actions: await this.actions.getAll(),
1326
1445
  sections: await this.actions.getAllSections()
1327
1446
  };
1328
1447
  }
1448
+ async reconnect() {
1449
+ await this.driver.reconnect();
1450
+ }
1329
1451
  async close() {
1330
1452
  await this.driver.close();
1331
1453
  }
@@ -1344,7 +1466,7 @@ var HarnessDB = class {
1344
1466
  return { added, skipped };
1345
1467
  }
1346
1468
  async writeFeatureList(cwd2) {
1347
- const allTasks = await this.tasks.getAll();
1469
+ const allTasks = await this.tasks.getAll(void 0, true);
1348
1470
  const list = await Promise.all(
1349
1471
  allTasks.map(async (t) => ({
1350
1472
  slug: t.slug,
@@ -1363,13 +1485,13 @@ async function openDB(config, cwd2) {
1363
1485
  const dbConfig = config.database;
1364
1486
  let driver;
1365
1487
  if (dbConfig.type === "postgres") {
1366
- const { PostgresDriver } = await import("./postgres-TYINLEAT.js");
1488
+ const { PostgresDriver } = await import("./postgres-6BXN7ZH4.js");
1367
1489
  driver = new PostgresDriver(dbConfig);
1368
1490
  } else if (dbConfig.type === "mysql") {
1369
- const { MySQLDriver } = await import("./mysql-IMDWH2CU.js");
1491
+ const { MySQLDriver } = await import("./mysql-NXLYFD2H.js");
1370
1492
  driver = new MySQLDriver(dbConfig);
1371
1493
  } else {
1372
- const { SQLiteDriver } = await import("./sqlite-5R6LB3RX.js");
1494
+ const { SQLiteDriver } = await import("./sqlite-M65L55DA.js");
1373
1495
  if (dbConfig.type !== "sqlite") {
1374
1496
  throw new Error("Invalid database type");
1375
1497
  }
@@ -1802,8 +1924,8 @@ async function runInit(cwd2, flags) {
1802
1924
  }
1803
1925
  firstTask = { title: taskTitle, description: taskDesc, acceptance };
1804
1926
  }
1805
- const spinner5 = p3.spinner();
1806
- spinner5.start("Scaffolding...");
1927
+ const spinner6 = p3.spinner();
1928
+ spinner6.start("Scaffolding...");
1807
1929
  try {
1808
1930
  const config = applyConfigDefaults({ name, description, provider, docsPath, tasksAdapter });
1809
1931
  const materializer = getMaterializer(provider);
@@ -1837,9 +1959,9 @@ async function runInit(cwd2, flags) {
1837
1959
  });
1838
1960
  }
1839
1961
  await db.close();
1840
- spinner5.stop("");
1962
+ spinner6.stop("");
1841
1963
  } catch (err) {
1842
- spinner5.stop("Failed");
1964
+ spinner6.stop("Failed");
1843
1965
  p3.log.error(err instanceof Error ? err.message : String(err));
1844
1966
  throw err;
1845
1967
  }
@@ -1900,16 +2022,16 @@ async function runMigrate(cwd2, opts) {
1900
2022
  console.log(pc7.dim(`Already on ${target} \u2014 nothing to migrate.`));
1901
2023
  return;
1902
2024
  }
1903
- const spinner5 = p4.spinner();
1904
- spinner5.start(`Migrating from ${config.provider} to ${target}...`);
2025
+ const spinner6 = p4.spinner();
2026
+ spinner6.start(`Migrating from ${config.provider} to ${target}...`);
1905
2027
  try {
1906
2028
  const targetMaterializer = getMaterializer(target);
1907
2029
  await targetMaterializer.build(config, cwd2);
1908
- spinner5.stop(pc7.green(`Migrated to ${target}`));
2030
+ spinner6.stop(pc7.green(`Migrated to ${target}`));
1909
2031
  p4.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
1910
2032
  p4.log.warn(`Then run: ahk build`);
1911
2033
  } catch (err) {
1912
- spinner5.stop(pc7.red("Migration failed"));
2034
+ spinner6.stop(pc7.red("Migration failed"));
1913
2035
  p4.log.error(err instanceof Error ? err.message : String(err));
1914
2036
  process.exit(1);
1915
2037
  }
@@ -2117,7 +2239,7 @@ var TOOLS = [
2117
2239
  },
2118
2240
  {
2119
2241
  name: "tasks.get",
2120
- description: "List tasks, optionally filtered by status.",
2242
+ description: "List tasks, optionally filtered by status. Excludes archived tasks by default.",
2121
2243
  inputSchema: {
2122
2244
  type: "object",
2123
2245
  properties: {
@@ -2125,6 +2247,10 @@ var TOOLS = [
2125
2247
  type: "string",
2126
2248
  enum: ["pending", "in_progress", "done", "blocked"],
2127
2249
  description: "Filter by status (omit for all tasks)"
2250
+ },
2251
+ includeArchived: {
2252
+ type: "boolean",
2253
+ description: "If true, include archived tasks in results"
2128
2254
  }
2129
2255
  }
2130
2256
  }
@@ -2227,6 +2353,46 @@ var TOOLS = [
2227
2353
  },
2228
2354
  required: ["actionId", "toolName"]
2229
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
+ }
2230
2396
  }
2231
2397
  ];
2232
2398
  async function startMcpServer(config, cwd2) {
@@ -2284,7 +2450,8 @@ async function dispatch(name, args, db, docsPath) {
2284
2450
  }
2285
2451
  case "tasks.get": {
2286
2452
  const status = args["status"];
2287
- 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);
2288
2455
  return ok(JSON.stringify(tasks, null, 2));
2289
2456
  }
2290
2457
  case "tasks.claim": {
@@ -2339,6 +2506,30 @@ async function dispatch(name, args, db, docsPath) {
2339
2506
  await db.recordTool(actionId, toolName, argsJson, resultSummary);
2340
2507
  return ok(JSON.stringify({ actionId, toolName, recorded: true }));
2341
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
+ }
2342
2533
  default:
2343
2534
  return ok(`Unknown tool: ${name}`, true);
2344
2535
  }
@@ -2384,8 +2575,8 @@ function collectMarkdownFiles(dir) {
2384
2575
  }
2385
2576
  return files;
2386
2577
  }
2387
- function ok(text3, isError = false) {
2388
- return { content: [{ type: "text", text: text3 }], isError };
2578
+ function ok(text4, isError = false) {
2579
+ return { content: [{ type: "text", text: text4 }], isError };
2389
2580
  }
2390
2581
  function str(args, key) {
2391
2582
  const v4 = args[key];
@@ -2432,7 +2623,8 @@ async function runStatus(cwd2, opts) {
2432
2623
  acceptance: await db.getTaskAcceptance(t.id)
2433
2624
  }))
2434
2625
  );
2435
- 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));
2436
2628
  return;
2437
2629
  }
2438
2630
  if (tasks.length === 0) {
@@ -2473,6 +2665,10 @@ async function runStatus(cwd2, opts) {
2473
2665
  return `${fn(s.status)}: ${s.total}`;
2474
2666
  });
2475
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
+ }
2476
2672
  } finally {
2477
2673
  await db.close();
2478
2674
  }
@@ -2569,8 +2765,8 @@ async function runTaskAdd(cwd2) {
2569
2765
  if (p6.isCancel(val) || !val || !val.trim()) break;
2570
2766
  acceptance.push(val.trim());
2571
2767
  }
2572
- const spinner5 = p6.spinner();
2573
- spinner5.start("Saving...");
2768
+ const spinner6 = p6.spinner();
2769
+ spinner6.start("Saving...");
2574
2770
  try {
2575
2771
  const config = await loadConfig(cwd2);
2576
2772
  const db = await openDB(config, cwd2);
@@ -2578,11 +2774,11 @@ async function runTaskAdd(cwd2) {
2578
2774
  const task2 = await db.addTask({ slug, title, description: description || void 0, acceptance });
2579
2775
  await db.writeFeatureList(cwd2);
2580
2776
  await db.close();
2581
- spinner5.stop("");
2777
+ spinner6.stop("");
2582
2778
  console.log(pc11.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
2583
2779
  console.log(pc11.cyan("\u2192") + " " + pc11.cyan("ahk status") + " to see all tasks");
2584
2780
  } catch (err) {
2585
- spinner5.stop(pc11.red("Failed"));
2781
+ spinner6.stop(pc11.red("Failed"));
2586
2782
  p6.log.error(err instanceof Error ? err.message : String(err));
2587
2783
  process.exit(1);
2588
2784
  }
@@ -2628,14 +2824,107 @@ async function runTaskDone(cwd2, idOrSlug) {
2628
2824
  }
2629
2825
  }
2630
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
+
2631
2920
  // src/commands/task/list.ts
2632
2921
  import Table2 from "cli-table3";
2633
- import pc13 from "picocolors";
2922
+ import pc14 from "picocolors";
2634
2923
  var STATUS_COLOR2 = {
2635
- pending: (s) => pc13.dim(s),
2636
- in_progress: (s) => pc13.cyan(s),
2637
- done: (s) => pc13.green(s),
2638
- 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)
2639
2928
  };
2640
2929
  async function runTaskList(cwd2, opts) {
2641
2930
  const config = await loadConfig(cwd2);
@@ -2643,17 +2932,20 @@ async function runTaskList(cwd2, opts) {
2643
2932
  try {
2644
2933
  const validStatuses = ["pending", "in_progress", "done", "blocked"];
2645
2934
  const filterStatus = opts.status && validStatuses.includes(opts.status) ? opts.status : void 0;
2646
- 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);
2647
2936
  if (opts.json) {
2648
2937
  console.log(JSON.stringify(tasks, null, 2));
2649
2938
  return;
2650
2939
  }
2651
2940
  if (tasks.length === 0) {
2652
- 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 + "."));
2653
2945
  return;
2654
2946
  }
2655
2947
  const table = new Table2({
2656
- head: ["ID", "Slug", "Title", "Status"].map((h) => pc13.bold(h)),
2948
+ head: ["ID", "Slug", "Title", "Status"].map((h) => pc14.bold(h)),
2657
2949
  style: { head: [], border: [] }
2658
2950
  });
2659
2951
  for (const t of tasks) {
@@ -2661,6 +2953,12 @@ async function runTaskList(cwd2, opts) {
2661
2953
  table.push([String(t.id), t.slug, t.title.slice(0, 50), colorFn(t.status)]);
2662
2954
  }
2663
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
+ }
2664
2962
  } finally {
2665
2963
  await db.close();
2666
2964
  }
@@ -2675,7 +2973,7 @@ var pkgPath = join16(dirname5(fileURLToPath3(import.meta.url)), "..", "package.j
2675
2973
  var pkg = require2(pkgPath);
2676
2974
 
2677
2975
  // src/core/update-check.ts
2678
- import pc14 from "picocolors";
2976
+ import pc15 from "picocolors";
2679
2977
  var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
2680
2978
  var TIMEOUT_MS = 2500;
2681
2979
  function checkForUpdate(currentVersion) {
@@ -2693,8 +2991,8 @@ function checkForUpdate(currentVersion) {
2693
2991
  }
2694
2992
  function printUpdateMessage({ current, latest }) {
2695
2993
  const lines = [
2696
- ` Update available ${pc14.dim(current)} \u2192 ${pc14.green(latest)} `,
2697
- ` 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}`)} `
2698
2996
  ];
2699
2997
  drawBox(lines);
2700
2998
  }
@@ -2734,12 +3032,15 @@ var task = program.command("task").description("Manage tasks");
2734
3032
  task.command("add").description("Add a task interactively").action(async () => {
2735
3033
  await runTaskAdd(cwd);
2736
3034
  });
2737
- 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) => {
2738
3036
  await runTaskList(cwd, opts);
2739
3037
  });
2740
3038
  task.command("done <id|slug>").description("Mark a task as done").action(async (idOrSlug) => {
2741
3039
  await runTaskDone(cwd, idOrSlug);
2742
3040
  });
3041
+ task.command("edit").description("Edit a task interactively").action(async () => {
3042
+ await runTaskEdit(cwd);
3043
+ });
2743
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) => {
2744
3045
  await runDashboard(cwd, { port: parseInt(opts.port), open: opts.open });
2745
3046
  });