@cardor/agent-harness-kit 2.1.0 → 2.1.1

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
@@ -16,11 +16,11 @@ import {
16
16
 
17
17
  // src/cli.ts
18
18
  import { Command, InvalidArgumentError } from "commander";
19
- import pc20 from "picocolors";
19
+ import pc21 from "picocolors";
20
20
 
21
21
  // src/commands/build.ts
22
22
  import { watch } from "fs";
23
- import * as p from "@clack/prompts";
23
+ import * as p2 from "@clack/prompts";
24
24
  import pc2 from "picocolors";
25
25
 
26
26
  // src/core/materializer/claude-code.ts
@@ -53,31 +53,39 @@ import { fileURLToPath } from "url";
53
53
  var require2 = createRequire(import.meta.url);
54
54
  var here = dirname(fileURLToPath(import.meta.url));
55
55
  var candidates = [join2(here, "..", "..", "package.json"), join2(here, "..", "package.json")];
56
- var pkgPath = candidates.find((p8) => existsSync(p8)) ?? candidates[0];
56
+ var pkgPath = candidates.find((p9) => existsSync(p9)) ?? candidates[0];
57
57
  var pkg = require2(pkgPath);
58
58
 
59
59
  // src/core/local-install-guard.ts
60
60
  function isLocalInstallSatisfied(cwd2) {
61
61
  const selfPkgPath = join3(cwd2, "package.json");
62
- let projectPkg = null;
63
62
  if (existsSync2(selfPkgPath)) {
64
63
  try {
65
64
  const selfPkg = JSON.parse(readFileSync(selfPkgPath, "utf8"));
66
65
  if (selfPkg?.name === pkg.name) return true;
67
- projectPkg = selfPkg;
68
66
  } catch {
69
67
  }
70
68
  }
69
+ return hasRealLocalInstall(cwd2);
70
+ }
71
+ function hasRealLocalInstall(cwd2) {
71
72
  const [scope, name] = pkg.name.split("/");
72
73
  const localPath = pkg.name.startsWith("@") ? join3(cwd2, "node_modules", scope, name) : join3(cwd2, "node_modules", pkg.name);
73
74
  if (existsSync2(localPath)) return true;
74
75
  const isPnp = existsSync2(join3(cwd2, ".pnp.cjs")) || existsSync2(join3(cwd2, ".pnp.loader.mjs"));
75
- if (isPnp && projectPkg) {
76
- const deps = {
77
- ...projectPkg.dependencies ?? {},
78
- ...projectPkg.devDependencies ?? {}
79
- };
80
- if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
76
+ if (isPnp) {
77
+ const pkgPath2 = join3(cwd2, "package.json");
78
+ if (existsSync2(pkgPath2)) {
79
+ try {
80
+ const projectPkg = JSON.parse(readFileSync(pkgPath2, "utf8"));
81
+ const deps = {
82
+ ...projectPkg?.dependencies ?? {},
83
+ ...projectPkg?.devDependencies ?? {}
84
+ };
85
+ if (Object.prototype.hasOwnProperty.call(deps, pkg.name)) return true;
86
+ } catch {
87
+ }
88
+ }
81
89
  }
82
90
  return false;
83
91
  }
@@ -132,7 +140,7 @@ function detectFromPackageManagerField(cwd2) {
132
140
  }
133
141
  function getMcpCommandParts(pm, port, cwd2) {
134
142
  const portStr = String(port);
135
- if (!isLocalInstallSatisfied(cwd2)) {
143
+ if (!hasRealLocalInstall(cwd2)) {
136
144
  return ["ahk", "serve", "--port", portStr];
137
145
  }
138
146
  switch (pm) {
@@ -1083,7 +1091,7 @@ No tasks in progress.
1083
1091
  ],
1084
1092
  { force: opts.force, backupRoot: join7(cwd2, config.storage.dir, "backups") }
1085
1093
  );
1086
- const agents = writeAgentFiles(cwd2, claudeAgentFiles(config), {
1094
+ const agents = writeAgentFiles(cwd2, claudeAgentFiles(config, opts.claudeAgentModels), {
1087
1095
  force: opts.force,
1088
1096
  backupRoot: join7(cwd2, config.storage.dir, "backups")
1089
1097
  });
@@ -1326,20 +1334,53 @@ function getMaterializer(provider) {
1326
1334
  }
1327
1335
  }
1328
1336
 
1337
+ // src/commands/claude-model-prompt.ts
1338
+ import * as p from "@clack/prompts";
1339
+ var AGENT_LABELS = [
1340
+ { key: "lead", label: "Lead" },
1341
+ { key: "explorer", label: "Explorer" },
1342
+ { key: "consultant", label: "Consultant" },
1343
+ { key: "builder", label: "Builder" },
1344
+ { key: "reviewer", label: "Reviewer" }
1345
+ ];
1346
+ async function promptClaudeAgentModels(provider) {
1347
+ const claudeAgentModels = {};
1348
+ if (provider !== "claude-code") return claudeAgentModels;
1349
+ for (const agent of AGENT_LABELS) {
1350
+ const val = await p.select({
1351
+ message: `Model for ${agent.label}`,
1352
+ options: [
1353
+ { value: "inherit", label: "inherit (default)" },
1354
+ { value: "haiku", label: "haiku" },
1355
+ { value: "sonnet", label: "sonnet" },
1356
+ { value: "opus", label: "opus" },
1357
+ { value: "fable", label: "fable" }
1358
+ ],
1359
+ initialValue: "inherit"
1360
+ });
1361
+ if (p.isCancel(val)) {
1362
+ p.cancel("Cancelled.");
1363
+ process.exit(0);
1364
+ }
1365
+ claudeAgentModels[agent.key] = val;
1366
+ }
1367
+ return claudeAgentModels;
1368
+ }
1369
+
1329
1370
  // src/commands/build.ts
1330
1371
  async function runBuild(cwd2, opts) {
1331
1372
  await buildOnce(cwd2, opts.force);
1332
1373
  if (opts.sync) {
1333
- p.log.step("Syncing agent permissions...");
1374
+ p2.log.step("Syncing agent permissions...");
1334
1375
  const config = await loadConfig(cwd2);
1335
1376
  const materializer = getMaterializer(config.provider);
1336
1377
  await materializer.syncPermissions(cwd2);
1337
1378
  }
1338
1379
  if (opts.watch) {
1339
- p.log.info(`Watching agent-harness-kit.config.ts for changes...`);
1380
+ p2.log.info(`Watching agent-harness-kit.config.ts for changes...`);
1340
1381
  watch(cwd2, { recursive: false }, async (_, filename) => {
1341
1382
  if (filename?.startsWith("agent-harness-kit.config")) {
1342
- p.log.step("Config changed \u2014 rebuilding...");
1383
+ p2.log.step("Config changed \u2014 rebuilding...");
1343
1384
  await buildOnce(cwd2, false);
1344
1385
  }
1345
1386
  });
@@ -1348,36 +1389,45 @@ async function runBuild(cwd2, opts) {
1348
1389
  }
1349
1390
  }
1350
1391
  async function buildOnce(cwd2, force) {
1351
- const spinner6 = p.spinner();
1352
- spinner6.start("Loading config...");
1392
+ let config;
1393
+ try {
1394
+ config = await loadConfig(cwd2);
1395
+ } catch (err) {
1396
+ p2.log.error(err instanceof Error ? err.message : String(err));
1397
+ process.exit(1);
1398
+ }
1399
+ let claudeAgentModels;
1400
+ if (force && config.provider === "claude-code") {
1401
+ claudeAgentModels = await promptClaudeAgentModels(config.provider);
1402
+ }
1403
+ const spinner6 = p2.spinner();
1404
+ spinner6.start("Rebuilding files...");
1353
1405
  try {
1354
- const config = await loadConfig(cwd2);
1355
- spinner6.message("Rebuilding files...");
1356
1406
  const materializer = getMaterializer(config.provider);
1357
- const report = await materializer.build(config, cwd2, { force });
1407
+ const report = await materializer.build(config, cwd2, { force, claudeAgentModels });
1358
1408
  spinner6.stop(pc2.green("Build complete"));
1359
1409
  const d = report.derived;
1360
1410
  const upToDate = [...d.created, ...d.current, ...d.propagated];
1361
1411
  if (upToDate.length > 0) {
1362
- p.log.success(upToDate.join(", "));
1412
+ p2.log.success(upToDate.join(", "));
1363
1413
  }
1364
1414
  if (d.propagated.length > 0) {
1365
- p.log.info(`Propagated config changes to ${d.propagated.length} generated file(s):
1415
+ p2.log.info(`Propagated config changes to ${d.propagated.length} generated file(s):
1366
1416
  ${d.propagated.join("\n ")}`);
1367
1417
  }
1368
1418
  if (d.overwritten.length > 0) {
1369
- p.log.warn(
1419
+ p2.log.warn(
1370
1420
  pc2.yellow(
1371
1421
  `--force REGENERATED ${d.overwritten.length} hand-edited generated file(s), discarding your edits:
1372
1422
  ` + d.overwritten.join("\n ")
1373
1423
  )
1374
1424
  );
1375
1425
  if (d.backupDir) {
1376
- p.log.info(pc2.yellow(` Previous content backed up \u2192 ${d.backupDir}`));
1426
+ p2.log.info(pc2.yellow(` Previous content backed up \u2192 ${d.backupDir}`));
1377
1427
  }
1378
1428
  }
1379
1429
  if (d.preserved.length > 0) {
1380
- p.log.warn(
1430
+ p2.log.warn(
1381
1431
  pc2.yellow(
1382
1432
  `Left ${d.preserved.length} hand-edited generated file(s) UNTOUCHED \u2014 your edits are safe:
1383
1433
  ` + d.preserved.join("\n ") + `
@@ -1386,26 +1436,26 @@ async function buildOnce(cwd2, force) {
1386
1436
  )
1387
1437
  );
1388
1438
  }
1389
- p.log.success(`Agent definitions (${config.provider})`);
1390
- p.log.success("MCP config");
1439
+ p2.log.success(`Agent definitions (${config.provider})`);
1440
+ p2.log.success("MCP config");
1391
1441
  const { created, overwritten, preserved, backupDir } = report.agents;
1392
1442
  if (created.length > 0) {
1393
- p.log.info(`Created ${created.length} missing agent file(s):
1443
+ p2.log.info(`Created ${created.length} missing agent file(s):
1394
1444
  ${created.join("\n ")}`);
1395
1445
  }
1396
1446
  if (overwritten.length > 0) {
1397
- p.log.warn(
1447
+ p2.log.warn(
1398
1448
  pc2.yellow(
1399
1449
  `--force REGENERATED ${overwritten.length} existing agent file(s), discarding any customizations:
1400
1450
  ` + overwritten.join("\n ")
1401
1451
  )
1402
1452
  );
1403
1453
  if (backupDir) {
1404
- p.log.info(pc2.yellow(` Previous content backed up \u2192 ${backupDir}`));
1454
+ p2.log.info(pc2.yellow(` Previous content backed up \u2192 ${backupDir}`));
1405
1455
  }
1406
1456
  }
1407
1457
  if (preserved.length > 0) {
1408
- p.log.info(
1458
+ p2.log.info(
1409
1459
  `Left ${preserved.length} existing agent file(s) untouched \u2014 agent files are yours to edit.
1410
1460
  Re-run with --force to regenerate them from the packaged templates (this DESTROYS your edits;
1411
1461
  a backup is written first).`
@@ -1413,7 +1463,7 @@ async function buildOnce(cwd2, force) {
1413
1463
  }
1414
1464
  } catch (err) {
1415
1465
  spinner6.stop(pc2.red("Build failed"));
1416
- p.log.error(err instanceof Error ? err.message : String(err));
1466
+ p2.log.error(err instanceof Error ? err.message : String(err));
1417
1467
  process.exit(1);
1418
1468
  }
1419
1469
  }
@@ -2027,7 +2077,7 @@ function getProviderHealthFiles(provider) {
2027
2077
  // src/commands/init.ts
2028
2078
  import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
2029
2079
  import { join as join16 } from "path";
2030
- import * as p3 from "@clack/prompts";
2080
+ import * as p4 from "@clack/prompts";
2031
2081
  import pc8 from "picocolors";
2032
2082
 
2033
2083
  // src/schema/init.ts
@@ -2066,7 +2116,7 @@ var taskDescriptionSchema = v2.pipe(
2066
2116
  );
2067
2117
 
2068
2118
  // src/utils/form.ts
2069
- import * as p2 from "@clack/prompts";
2119
+ import * as p3 from "@clack/prompts";
2070
2120
  import * as v3 from "valibot";
2071
2121
  var cliFormWithRetry = async (formFn, schema) => {
2072
2122
  while (true) {
@@ -2074,8 +2124,8 @@ var cliFormWithRetry = async (formFn, schema) => {
2074
2124
  const result = v3.safeParse(schema, res);
2075
2125
  if (result.success) return result.output;
2076
2126
  const messages = result.issues.map((i) => i.message).join(", ");
2077
- p2.log.error(messages);
2078
- p2.log.info("Please try again.\n");
2127
+ p3.log.error(messages);
2128
+ p3.log.info("Please try again.\n");
2079
2129
  }
2080
2130
  };
2081
2131
 
@@ -2236,25 +2286,25 @@ async function runInit(cwd2, flags) {
2236
2286
  name = flags.name;
2237
2287
  } else {
2238
2288
  name = await cliFormWithRetry(async () => {
2239
- const val = await p3.text({
2289
+ const val = await p4.text({
2240
2290
  message: "Project name",
2241
2291
  placeholder: "my-app",
2242
2292
  ...detectedName && { initialValue: detectedName }
2243
2293
  });
2244
- if (p3.isCancel(val)) {
2245
- p3.cancel("Cancelled.");
2294
+ if (p4.isCancel(val)) {
2295
+ p4.cancel("Cancelled.");
2246
2296
  process.exit(0);
2247
2297
  }
2248
2298
  return val;
2249
2299
  }, initNameSchema);
2250
2300
  }
2251
2301
  const description = await cliFormWithRetry(async () => {
2252
- const val = await p3.text({
2302
+ const val = await p4.text({
2253
2303
  message: "Short description (shown to agents as context)",
2254
2304
  placeholder: "A REST API for managing notes"
2255
2305
  });
2256
- if (p3.isCancel(val)) {
2257
- p3.cancel("Cancelled.");
2306
+ if (p4.isCancel(val)) {
2307
+ p4.cancel("Cancelled.");
2258
2308
  process.exit(0);
2259
2309
  }
2260
2310
  return val;
@@ -2263,7 +2313,7 @@ async function runInit(cwd2, flags) {
2263
2313
  if (flags.provider && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(flags.provider)) {
2264
2314
  provider = flags.provider;
2265
2315
  } else {
2266
- const val = await p3.select({
2316
+ const val = await p4.select({
2267
2317
  message: "AI provider",
2268
2318
  options: [
2269
2319
  { value: "opencode", label: "OpenCode" },
@@ -2272,51 +2322,24 @@ async function runInit(cwd2, flags) {
2272
2322
  { value: "grok-cli", label: "Grok CLI" }
2273
2323
  ]
2274
2324
  });
2275
- if (p3.isCancel(val)) {
2276
- p3.cancel("Cancelled.");
2325
+ if (p4.isCancel(val)) {
2326
+ p4.cancel("Cancelled.");
2277
2327
  process.exit(0);
2278
2328
  }
2279
2329
  provider = val;
2280
2330
  }
2281
- const AGENT_LABELS = [
2282
- { key: "lead", label: "Lead" },
2283
- { key: "explorer", label: "Explorer" },
2284
- { key: "consultant", label: "Consultant" },
2285
- { key: "builder", label: "Builder" },
2286
- { key: "reviewer", label: "Reviewer" }
2287
- ];
2288
- const claudeAgentModels = {};
2289
- if (provider === "claude-code") {
2290
- for (const agent of AGENT_LABELS) {
2291
- const val = await p3.select({
2292
- message: `Model for ${agent.label}`,
2293
- options: [
2294
- { value: "inherit", label: "inherit (default)" },
2295
- { value: "haiku", label: "haiku" },
2296
- { value: "sonnet", label: "sonnet" },
2297
- { value: "opus", label: "opus" },
2298
- { value: "fable", label: "fable" }
2299
- ],
2300
- initialValue: "inherit"
2301
- });
2302
- if (p3.isCancel(val)) {
2303
- p3.cancel("Cancelled.");
2304
- process.exit(0);
2305
- }
2306
- claudeAgentModels[agent.key] = val;
2307
- }
2308
- }
2331
+ const claudeAgentModels = await promptClaudeAgentModels(provider);
2309
2332
  let docsPath;
2310
2333
  if (flags.docs) {
2311
2334
  docsPath = flags.docs;
2312
2335
  } else {
2313
2336
  docsPath = await cliFormWithRetry(async () => {
2314
- const val = await p3.text({
2337
+ const val = await p4.text({
2315
2338
  message: "Docs folder path (agents will search here)",
2316
2339
  initialValue: "./docs"
2317
2340
  });
2318
- if (p3.isCancel(val)) {
2319
- p3.cancel("Cancelled.");
2341
+ if (p4.isCancel(val)) {
2342
+ p4.cancel("Cancelled.");
2320
2343
  process.exit(0);
2321
2344
  }
2322
2345
  return val;
@@ -2326,7 +2349,7 @@ async function runInit(cwd2, flags) {
2326
2349
  if (flags.storageScope && ["local", "global"].includes(flags.storageScope)) {
2327
2350
  storageScope = flags.storageScope;
2328
2351
  } else {
2329
- const val = await p3.select({
2352
+ const val = await p4.select({
2330
2353
  message: "Storage scope",
2331
2354
  options: [
2332
2355
  { value: "local", label: "Local \u2014 .harness/harness.db lives in this project" },
@@ -2337,8 +2360,8 @@ async function runInit(cwd2, flags) {
2337
2360
  ],
2338
2361
  initialValue: "local"
2339
2362
  });
2340
- if (p3.isCancel(val)) {
2341
- p3.cancel("Cancelled.");
2363
+ if (p4.isCancel(val)) {
2364
+ p4.cancel("Cancelled.");
2342
2365
  process.exit(0);
2343
2366
  }
2344
2367
  storageScope = val;
@@ -2347,7 +2370,7 @@ async function runInit(cwd2, flags) {
2347
2370
  if (flags.tasks && ["local", "jira", "linear"].includes(flags.tasks)) {
2348
2371
  tasksAdapter = flags.tasks;
2349
2372
  } else {
2350
- const val = await p3.select({
2373
+ const val = await p4.select({
2351
2374
  message: "Task adapter",
2352
2375
  options: [
2353
2376
  { value: "local", label: "Local (feature_list.json)" },
@@ -2355,50 +2378,50 @@ async function runInit(cwd2, flags) {
2355
2378
  { value: "linear", label: "Linear (coming soon)" }
2356
2379
  ]
2357
2380
  });
2358
- if (p3.isCancel(val)) {
2359
- p3.cancel("Cancelled");
2381
+ if (p4.isCancel(val)) {
2382
+ p4.cancel("Cancelled");
2360
2383
  process.exit(0);
2361
2384
  }
2362
2385
  tasksAdapter = val;
2363
2386
  }
2364
- const addFirstTask = await p3.confirm({ message: "Add your first task now?", initialValue: false });
2365
- if (p3.isCancel(addFirstTask)) {
2366
- p3.cancel("Cancelled");
2387
+ const addFirstTask = await p4.confirm({ message: "Add your first task now?", initialValue: false });
2388
+ if (p4.isCancel(addFirstTask)) {
2389
+ p4.cancel("Cancelled");
2367
2390
  process.exit(0);
2368
2391
  }
2369
2392
  let firstTask;
2370
2393
  if (addFirstTask) {
2371
2394
  const taskTitle = await cliFormWithRetry(async () => {
2372
- const val = await p3.text({ message: "Task title" });
2373
- if (p3.isCancel(val)) {
2374
- p3.cancel("Cancelled");
2395
+ const val = await p4.text({ message: "Task title" });
2396
+ if (p4.isCancel(val)) {
2397
+ p4.cancel("Cancelled");
2375
2398
  process.exit(0);
2376
2399
  }
2377
2400
  return val.trim();
2378
2401
  }, taskTitleSchema);
2379
2402
  const taskDesc = await cliFormWithRetry(async () => {
2380
- const val = await p3.text({ message: "Task description", placeholder: "What and why" });
2381
- if (p3.isCancel(val)) {
2382
- p3.cancel("Cancelled");
2403
+ const val = await p4.text({ message: "Task description", placeholder: "What and why" });
2404
+ if (p4.isCancel(val)) {
2405
+ p4.cancel("Cancelled");
2383
2406
  process.exit(0);
2384
2407
  }
2385
2408
  return val.trim();
2386
2409
  }, taskDescriptionSchema);
2387
2410
  const acceptance = [];
2388
- p3.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
2411
+ p4.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
2389
2412
  while (true) {
2390
- const criterionVal = await p3.text({
2413
+ const criterionVal = await p4.text({
2391
2414
  message: ">",
2392
2415
  placeholder: "Criterion (or press Enter to finish)"
2393
2416
  });
2394
- if (p3.isCancel(criterionVal) || !criterionVal || !criterionVal.trim()) break;
2417
+ if (p4.isCancel(criterionVal) || !criterionVal || !criterionVal.trim()) break;
2395
2418
  acceptance.push(criterionVal.trim());
2396
2419
  }
2397
2420
  firstTask = { title: taskTitle, description: taskDesc, acceptance };
2398
2421
  }
2399
2422
  let configExt = "ts";
2400
2423
  let featureListParseFailedPath = null;
2401
- const spinner6 = p3.spinner();
2424
+ const spinner6 = p4.spinner();
2402
2425
  spinner6.start("Scaffolding...");
2403
2426
  try {
2404
2427
  const config = applyConfigDefaults({
@@ -2437,7 +2460,7 @@ async function runInit(cwd2, flags) {
2437
2460
  spinner6.stop("");
2438
2461
  } catch (err) {
2439
2462
  spinner6.stop("Failed");
2440
- p3.log.error(err instanceof Error ? err.message : String(err));
2463
+ p4.log.error(err instanceof Error ? err.message : String(err));
2441
2464
  throw err;
2442
2465
  }
2443
2466
  if (featureListParseFailedPath) {
@@ -2489,7 +2512,7 @@ async function runInit(cwd2, flags) {
2489
2512
  }
2490
2513
 
2491
2514
  // src/commands/migrate.ts
2492
- import * as p4 from "@clack/prompts";
2515
+ import * as p5 from "@clack/prompts";
2493
2516
  import pc9 from "picocolors";
2494
2517
  async function runMigrate(cwd2, opts) {
2495
2518
  const config = await loadConfig(cwd2);
@@ -2497,7 +2520,7 @@ async function runMigrate(cwd2, opts) {
2497
2520
  if (opts.to && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(opts.to)) {
2498
2521
  target = opts.to;
2499
2522
  } else {
2500
- const val = await p4.select({
2523
+ const val = await p5.select({
2501
2524
  message: "Migrate to provider",
2502
2525
  options: [
2503
2526
  { value: "claude-code", label: "Claude Code" },
@@ -2506,8 +2529,8 @@ async function runMigrate(cwd2, opts) {
2506
2529
  { value: "grok-cli", label: "Grok CLI" }
2507
2530
  ]
2508
2531
  });
2509
- if (p4.isCancel(val)) {
2510
- p4.cancel("Cancelled.");
2532
+ if (p5.isCancel(val)) {
2533
+ p5.cancel("Cancelled.");
2511
2534
  process.exit(0);
2512
2535
  }
2513
2536
  target = val;
@@ -2516,17 +2539,17 @@ async function runMigrate(cwd2, opts) {
2516
2539
  console.log(pc9.dim(`Already on ${target} \u2014 nothing to migrate.`));
2517
2540
  return;
2518
2541
  }
2519
- const spinner6 = p4.spinner();
2542
+ const spinner6 = p5.spinner();
2520
2543
  spinner6.start(`Migrating from ${config.provider} to ${target}...`);
2521
2544
  try {
2522
2545
  const targetMaterializer = getMaterializer(target);
2523
2546
  await targetMaterializer.build(config, cwd2);
2524
2547
  spinner6.stop(pc9.green(`Migrated to ${target}`));
2525
- p4.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
2526
- p4.log.warn(`Then run: ahk build`);
2548
+ p5.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
2549
+ p5.log.warn(`Then run: ahk build`);
2527
2550
  } catch (err) {
2528
2551
  spinner6.stop(pc9.red("Migration failed"));
2529
- p4.log.error(err instanceof Error ? err.message : String(err));
2552
+ p5.log.error(err instanceof Error ? err.message : String(err));
2530
2553
  process.exit(1);
2531
2554
  }
2532
2555
  }
@@ -2771,12 +2794,65 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
2771
2794
  }
2772
2795
  }
2773
2796
 
2797
+ // src/commands/models.ts
2798
+ import { join as join18 } from "path";
2799
+ import pc11 from "picocolors";
2800
+ async function resolveModelsContext(cwd2) {
2801
+ let config;
2802
+ try {
2803
+ config = await loadConfig(cwd2);
2804
+ } catch {
2805
+ return { ok: false, reason: "no-config" };
2806
+ }
2807
+ if (config.provider !== "claude-code") {
2808
+ return { ok: false, reason: "not-claude-code", provider: config.provider };
2809
+ }
2810
+ return { ok: true, config };
2811
+ }
2812
+ async function runModels(cwd2) {
2813
+ const ctx = await resolveModelsContext(cwd2);
2814
+ if (!ctx.ok) {
2815
+ if (ctx.reason === "no-config") {
2816
+ console.log("");
2817
+ console.log(` ${pc11.cyan("config".padEnd(16))}${pc11.yellow("[!]")} no agent-harness-kit.config found`);
2818
+ console.log(` ${"".padEnd(16)} ${pc11.dim("run: ahk init")}`);
2819
+ console.log("");
2820
+ return;
2821
+ }
2822
+ console.log(
2823
+ pc11.dim(
2824
+ `ahk models only applies to Claude Code projects (this project uses '${ctx.provider}') \u2014 nothing to do.`
2825
+ )
2826
+ );
2827
+ return;
2828
+ }
2829
+ const { config } = ctx;
2830
+ const claudeAgentModels = await promptClaudeAgentModels(config.provider);
2831
+ const agents = writeAgentFiles(cwd2, claudeAgentFiles(config, claudeAgentModels), {
2832
+ force: true,
2833
+ backupRoot: join18(cwd2, config.storage.dir, "backups")
2834
+ });
2835
+ console.log("");
2836
+ if (agents.overwritten.length > 0) {
2837
+ console.log(pc11.green(`\u2713 Regenerated ${agents.overwritten.length} agent file(s) with updated models:`));
2838
+ for (const file of agents.overwritten) console.log(pc11.green(` \u2713 ${file}`));
2839
+ if (agents.backupDir) {
2840
+ console.log(pc11.dim(` Previous content backed up \u2192 ${agents.backupDir}`));
2841
+ }
2842
+ }
2843
+ if (agents.created.length > 0) {
2844
+ console.log(pc11.green(`\u2713 Created ${agents.created.length} missing agent file(s):`));
2845
+ for (const file of agents.created) console.log(pc11.green(` \u2713 ${file}`));
2846
+ }
2847
+ console.log("");
2848
+ }
2849
+
2774
2850
  // src/commands/reset.ts
2775
2851
  import { existsSync as existsSync16, readdirSync, rmSync as rmSync2 } from "fs";
2776
2852
  import { homedir as homedir4 } from "os";
2777
- import { join as join18, resolve as resolve8 } from "path";
2778
- import * as p5 from "@clack/prompts";
2779
- import pc11 from "picocolors";
2853
+ import { join as join19, resolve as resolve8 } from "path";
2854
+ import * as p6 from "@clack/prompts";
2855
+ import pc12 from "picocolors";
2780
2856
  var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
2781
2857
  var PROVIDER_AGENT_DIRS = {
2782
2858
  "claude-code": ".claude/agents",
@@ -2795,7 +2871,7 @@ async function resetAgentMds(cwd2, provider) {
2795
2871
  const agentDirPath = resolve8(cwd2, agentDir);
2796
2872
  const agentExt = PROVIDER_AGENT_EXT[provider];
2797
2873
  if (!existsSync16(agentDirPath)) {
2798
- console.log(pc11.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
2874
+ console.log(pc12.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
2799
2875
  return;
2800
2876
  }
2801
2877
  const existingFiles = [];
@@ -2807,32 +2883,32 @@ async function resetAgentMds(cwd2, provider) {
2807
2883
  }
2808
2884
  }
2809
2885
  } catch {
2810
- console.log(pc11.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
2886
+ console.log(pc12.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
2811
2887
  return;
2812
2888
  }
2813
2889
  if (existingFiles.length === 0) {
2814
- console.log(pc11.yellow(` No agent MD files found in ${agentDir}/`));
2890
+ console.log(pc12.yellow(` No agent MD files found in ${agentDir}/`));
2815
2891
  return;
2816
2892
  }
2817
2893
  for (const file of existingFiles) {
2818
- const confirm3 = await p5.confirm({
2894
+ const confirm3 = await p6.confirm({
2819
2895
  message: `Remove ${file}?`,
2820
2896
  initialValue: true
2821
2897
  });
2822
- if (p5.isCancel(confirm3)) {
2823
- console.log(pc11.red(" Cancelled by user."));
2898
+ if (p6.isCancel(confirm3)) {
2899
+ console.log(pc12.red(" Cancelled by user."));
2824
2900
  return;
2825
2901
  }
2826
2902
  if (confirm3) {
2827
2903
  try {
2828
- const filePath = join18(agentDirPath, file);
2904
+ const filePath = join19(agentDirPath, file);
2829
2905
  rmSync2(filePath, { force: true });
2830
- console.log(pc11.green(` Removed ${file}`));
2906
+ console.log(pc12.green(` Removed ${file}`));
2831
2907
  } catch {
2832
- console.error(pc11.red(` Failed to remove ${file}`));
2908
+ console.error(pc12.red(` Failed to remove ${file}`));
2833
2909
  }
2834
2910
  } else {
2835
- console.log(pc11.cyan(` Skipped ${file}`));
2911
+ console.log(pc12.cyan(` Skipped ${file}`));
2836
2912
  }
2837
2913
  }
2838
2914
  }
@@ -2841,7 +2917,7 @@ async function runReset(cwd2, opts) {
2841
2917
  try {
2842
2918
  config = await loadConfig(cwd2);
2843
2919
  } catch {
2844
- console.error(pc11.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
2920
+ console.error(pc12.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
2845
2921
  process.exit(1);
2846
2922
  }
2847
2923
  const storageDir = config.storage.dir || ".harness";
@@ -2855,33 +2931,33 @@ async function runReset(cwd2, opts) {
2855
2931
  resetDb = true;
2856
2932
  } else {
2857
2933
  if (config.database.type !== "sqlite") {
2858
- console.log(pc11.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
2934
+ console.log(pc12.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
2859
2935
  resetDb = false;
2860
2936
  } else {
2861
- const confirm3 = await p5.confirm({
2937
+ const confirm3 = await p6.confirm({
2862
2938
  message: `Delete database (${dbPath})?`,
2863
2939
  initialValue: true
2864
2940
  });
2865
- if (p5.isCancel(confirm3)) {
2866
- console.log(pc11.red(" Cancelled by user."));
2941
+ if (p6.isCancel(confirm3)) {
2942
+ console.log(pc12.red(" Cancelled by user."));
2867
2943
  return;
2868
2944
  }
2869
2945
  resetDb = confirm3;
2870
2946
  }
2871
2947
  }
2872
2948
  } else if (!dbPath) {
2873
- console.log(pc11.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
2949
+ console.log(pc12.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
2874
2950
  }
2875
2951
  if (existsSync16(featureListPath)) {
2876
2952
  if (opts.force) {
2877
2953
  resetFeatureList = true;
2878
2954
  } else {
2879
- const confirm3 = await p5.confirm({
2955
+ const confirm3 = await p6.confirm({
2880
2956
  message: `Delete feature list (${storageDir}/feature_list.json)?`,
2881
2957
  initialValue: true
2882
2958
  });
2883
- if (p5.isCancel(confirm3)) {
2884
- console.log(pc11.red(" Cancelled by user."));
2959
+ if (p6.isCancel(confirm3)) {
2960
+ console.log(pc12.red(" Cancelled by user."));
2885
2961
  return;
2886
2962
  }
2887
2963
  resetFeatureList = confirm3;
@@ -2895,17 +2971,17 @@ async function runReset(cwd2, opts) {
2895
2971
  rmSync2(dbPath, { force: true });
2896
2972
  rmSync2(`${dbPath}-wal`, { force: true });
2897
2973
  rmSync2(`${dbPath}-shm`, { force: true });
2898
- console.log(pc11.green(` \u2713 Removed ${dbPath}`));
2974
+ console.log(pc12.green(` \u2713 Removed ${dbPath}`));
2899
2975
  } catch {
2900
- console.error(pc11.red(` \u2717 Failed to remove ${dbPath}`));
2976
+ console.error(pc12.red(` \u2717 Failed to remove ${dbPath}`));
2901
2977
  }
2902
2978
  }
2903
2979
  if (resetFeatureList) {
2904
2980
  try {
2905
2981
  rmSync2(featureListPath, { force: true });
2906
- console.log(pc11.green(` \u2713 Removed ${storageDir}/feature_list.json`));
2982
+ console.log(pc12.green(` \u2713 Removed ${storageDir}/feature_list.json`));
2907
2983
  } catch {
2908
- console.error(pc11.red(` \u2717 Failed to remove ${featureListPath}`));
2984
+ console.error(pc12.red(` \u2717 Failed to remove ${featureListPath}`));
2909
2985
  }
2910
2986
  }
2911
2987
  if (resetAgentMdsFlag) {
@@ -2913,16 +2989,16 @@ async function runReset(cwd2, opts) {
2913
2989
  await resetAgentMds(cwd2, opts.provider || "claude-code");
2914
2990
  }
2915
2991
  if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
2916
- console.log(pc11.yellow(" Nothing to reset (all items missing or skipped)."));
2992
+ console.log(pc12.yellow(" Nothing to reset (all items missing or skipped)."));
2917
2993
  return;
2918
2994
  }
2919
2995
  console.log("");
2920
- console.log(pc11.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
2996
+ console.log(pc12.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
2921
2997
  }
2922
2998
 
2923
2999
  // src/core/mcp-server.ts
2924
3000
  import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync10 } from "fs";
2925
- import { join as join20, resolve as resolve9 } from "path";
3001
+ import { join as join21, resolve as resolve9 } from "path";
2926
3002
  import { Server } from "@modelcontextprotocol/sdk/server";
2927
3003
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2928
3004
  import {
@@ -2932,7 +3008,7 @@ import {
2932
3008
 
2933
3009
  // src/core/permissions-check.ts
2934
3010
  import { existsSync as existsSync17 } from "fs";
2935
- import { join as join19 } from "path";
3011
+ import { join as join20 } from "path";
2936
3012
  var AGENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
2937
3013
  function checkPermissionsSync(cwd2, config) {
2938
3014
  if (config.provider !== "claude-code") {
@@ -2941,7 +3017,7 @@ function checkPermissionsSync(cwd2, config) {
2941
3017
  const agents = {};
2942
3018
  let in_sync = true;
2943
3019
  for (const agent of AGENTS) {
2944
- const filePath = join19(cwd2, ".claude", "agents", `${agent}.md`);
3020
+ const filePath = join20(cwd2, ".claude", "agents", `${agent}.md`);
2945
3021
  const exists = existsSync17(filePath);
2946
3022
  if (!exists) in_sync = false;
2947
3023
  agents[agent] = exists ? { ok: true } : { ok: false, reason: "missing_file" };
@@ -3403,7 +3479,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
3403
3479
  return ok2(JSON.stringify(result));
3404
3480
  }
3405
3481
  case "deps.snapshot": {
3406
- const pkgPath2 = join20(cwd2, "package.json");
3482
+ const pkgPath2 = join21(cwd2, "package.json");
3407
3483
  if (!existsSync18(pkgPath2)) {
3408
3484
  return ok2("package.json not found in project root", true);
3409
3485
  }
@@ -3413,9 +3489,9 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
3413
3489
  dependencies: pkg2.dependencies ?? {},
3414
3490
  devDependencies: pkg2.devDependencies ?? {}
3415
3491
  };
3416
- const harnessDir = join20(cwd2, ".harness");
3492
+ const harnessDir = join21(cwd2, ".harness");
3417
3493
  mkdirSync9(harnessDir, { recursive: true });
3418
- writeFileSync10(join20(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
3494
+ writeFileSync10(join21(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
3419
3495
  return ok2(
3420
3496
  JSON.stringify({
3421
3497
  message: "Snapshot saved to .harness/deps-lock.json",
@@ -3424,8 +3500,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
3424
3500
  );
3425
3501
  }
3426
3502
  case "deps.check": {
3427
- const pkgPath2 = join20(cwd2, "package.json");
3428
- const lockPath = join20(cwd2, ".harness", "deps-lock.json");
3503
+ const pkgPath2 = join21(cwd2, "package.json");
3504
+ const lockPath = join21(cwd2, ".harness", "deps-lock.json");
3429
3505
  if (!existsSync18(pkgPath2)) {
3430
3506
  return ok2("package.json not found in project root", true);
3431
3507
  }
@@ -3531,7 +3607,7 @@ function collectMarkdownFiles(dir) {
3531
3607
  const files = [];
3532
3608
  try {
3533
3609
  for (const entry of readdirSync2(dir)) {
3534
- const full = join20(dir, entry);
3610
+ const full = join21(dir, entry);
3535
3611
  const stat = statSync(full);
3536
3612
  if (stat.isDirectory()) {
3537
3613
  files.push(...collectMarkdownFiles(full));
@@ -3588,12 +3664,12 @@ async function runServe(cwd2, opts) {
3588
3664
 
3589
3665
  // src/commands/status.ts
3590
3666
  import Table from "cli-table3";
3591
- import pc12 from "picocolors";
3667
+ import pc13 from "picocolors";
3592
3668
  var STATUS_COLOR = {
3593
- pending: (s) => pc12.dim(s),
3594
- in_progress: (s) => pc12.cyan(s),
3595
- done: (s) => pc12.green(s),
3596
- blocked: (s) => pc12.red(s)
3669
+ pending: (s) => pc13.dim(s),
3670
+ in_progress: (s) => pc13.cyan(s),
3671
+ done: (s) => pc13.green(s),
3672
+ blocked: (s) => pc13.red(s)
3597
3673
  };
3598
3674
  async function runStatus(cwd2, opts) {
3599
3675
  const config = await loadConfig(cwd2);
@@ -3614,11 +3690,11 @@ async function runStatus(cwd2, opts) {
3614
3690
  return;
3615
3691
  }
3616
3692
  if (tasks.length === 0) {
3617
- console.log(pc12.dim("No tasks yet. Run: ahk task add"));
3693
+ console.log(pc13.dim("No tasks yet. Run: ahk task add"));
3618
3694
  return;
3619
3695
  }
3620
3696
  const table = new Table({
3621
- head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc12.bold(h)),
3697
+ head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc13.bold(h)),
3622
3698
  style: { head: [], border: [] }
3623
3699
  });
3624
3700
  for (const t of tasks) {
@@ -3636,12 +3712,12 @@ async function runStatus(cwd2, opts) {
3636
3712
  const inProgress = tasks.filter((t) => t.status === "in_progress");
3637
3713
  if (inProgress.length > 0) {
3638
3714
  console.log("");
3639
- console.log(pc12.bold("Active actions:"));
3715
+ console.log(pc13.bold("Active actions:"));
3640
3716
  for (const t of inProgress) {
3641
3717
  const actions = await db.getActionsForTask(t.id);
3642
3718
  const active = actions.filter((a) => a.status === "in_progress");
3643
3719
  for (const a of active) {
3644
- console.log(` ${pc12.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
3720
+ console.log(` ${pc13.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
3645
3721
  }
3646
3722
  }
3647
3723
  }
@@ -3650,10 +3726,10 @@ async function runStatus(cwd2, opts) {
3650
3726
  const fn = STATUS_COLOR[s.status] ?? ((x) => x);
3651
3727
  return `${fn(s.status)}: ${s.total}`;
3652
3728
  });
3653
- console.log(pc12.dim("Tasks \u2014 ") + parts.join(pc12.dim(" | ")));
3729
+ console.log(pc13.dim("Tasks \u2014 ") + parts.join(pc13.dim(" | ")));
3654
3730
  const archivedTasks = await db.getArchivedTasks();
3655
3731
  if (archivedTasks.length > 0) {
3656
- console.log(pc12.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
3732
+ console.log(pc13.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
3657
3733
  }
3658
3734
  } finally {
3659
3735
  await db.close();
@@ -3662,12 +3738,12 @@ async function runStatus(cwd2, opts) {
3662
3738
 
3663
3739
  // src/commands/sync.ts
3664
3740
  import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
3665
- import { join as join21, resolve as resolve10 } from "path";
3666
- import pc13 from "picocolors";
3741
+ import { join as join22, resolve as resolve10 } from "path";
3742
+ import pc14 from "picocolors";
3667
3743
  async function runSync(cwd2, opts) {
3668
3744
  const config = await loadConfig(cwd2);
3669
3745
  const direction = opts.direction ?? "both";
3670
- const featureListPath = resolve10(join21(cwd2, config.storage.dir, "feature_list.json"));
3746
+ const featureListPath = resolve10(join22(cwd2, config.storage.dir, "feature_list.json"));
3671
3747
  const db = await openDB(config, cwd2);
3672
3748
  try {
3673
3749
  if (direction === "in" || direction === "both") {
@@ -3682,48 +3758,48 @@ async function runSync(cwd2, opts) {
3682
3758
  }
3683
3759
  async function syncIn(featureListPath, db, dryRun) {
3684
3760
  if (!existsSync19(featureListPath)) {
3685
- console.log(pc13.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
3761
+ console.log(pc14.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
3686
3762
  return;
3687
3763
  }
3688
3764
  let seeds;
3689
3765
  try {
3690
3766
  seeds = JSON.parse(readFileSync11(featureListPath, "utf8"));
3691
3767
  } catch (err) {
3692
- console.error(pc13.red(`Failed to parse feature_list.json: ${err}`));
3768
+ console.error(pc14.red(`Failed to parse feature_list.json: ${err}`));
3693
3769
  process.exit(1);
3694
3770
  }
3695
3771
  if (dryRun) {
3696
- console.log(pc13.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
3772
+ console.log(pc14.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
3697
3773
  for (const t of seeds) {
3698
3774
  const existing = await db.getTaskBySlug(t.slug);
3699
- console.log(` ${existing ? pc13.dim("skip") : pc13.green("add ")} ${t.slug}`);
3775
+ console.log(` ${existing ? pc14.dim("skip") : pc14.green("add ")} ${t.slug}`);
3700
3776
  }
3701
3777
  return;
3702
3778
  }
3703
3779
  const result = await db.syncFromFeatureList(seeds);
3704
- console.log(pc13.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
3780
+ console.log(pc14.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
3705
3781
  }
3706
3782
  async function syncOut(db, cwd2, dryRun) {
3707
3783
  if (dryRun) {
3708
3784
  const tasks = await db.getTasks();
3709
- console.log(pc13.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
3785
+ console.log(pc14.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
3710
3786
  console.log(` ${tasks.length} tasks would be written`);
3711
3787
  return;
3712
3788
  }
3713
3789
  await db.writeFeatureList(cwd2);
3714
- console.log(pc13.green("\u2713 Out-sync: feature_list.json updated"));
3790
+ console.log(pc14.green("\u2713 Out-sync: feature_list.json updated"));
3715
3791
  }
3716
3792
 
3717
3793
  // src/commands/task/add.ts
3718
- import * as p6 from "@clack/prompts";
3719
- import pc14 from "picocolors";
3794
+ import * as p7 from "@clack/prompts";
3795
+ import pc15 from "picocolors";
3720
3796
  async function runTaskAdd(cwd2) {
3721
- p6.intro(pc14.bold("agent-harness-kit \u2014 add task"));
3797
+ p7.intro(pc15.bold("agent-harness-kit \u2014 add task"));
3722
3798
  const title = await cliFormWithRetry(
3723
3799
  async () => {
3724
- const val = await p6.text({ message: "Task title" });
3725
- if (p6.isCancel(val)) {
3726
- p6.cancel("Cancelled.");
3800
+ const val = await p7.text({ message: "Task title" });
3801
+ if (p7.isCancel(val)) {
3802
+ p7.cancel("Cancelled.");
3727
3803
  process.exit(0);
3728
3804
  }
3729
3805
  return val.trim();
@@ -3732,12 +3808,12 @@ async function runTaskAdd(cwd2) {
3732
3808
  );
3733
3809
  const description = await cliFormWithRetry(
3734
3810
  async () => {
3735
- const val = await p6.text({
3811
+ const val = await p7.text({
3736
3812
  message: "Description (what and why)",
3737
3813
  placeholder: "Describe the task in more detail, including any relevant context or instructions for the agents."
3738
3814
  });
3739
- if (p6.isCancel(val)) {
3740
- p6.cancel("Cancelled.");
3815
+ if (p7.isCancel(val)) {
3816
+ p7.cancel("Cancelled.");
3741
3817
  process.exit(0);
3742
3818
  }
3743
3819
  return val.trim();
@@ -3745,13 +3821,13 @@ async function runTaskAdd(cwd2) {
3745
3821
  taskDescriptionSchema
3746
3822
  );
3747
3823
  const acceptance = [];
3748
- p6.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
3824
+ p7.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
3749
3825
  while (true) {
3750
- const val = await p6.text({ message: ">", placeholder: "Criterion (or press Enter to finish)" });
3751
- if (p6.isCancel(val) || !val || !val.trim()) break;
3826
+ const val = await p7.text({ message: ">", placeholder: "Criterion (or press Enter to finish)" });
3827
+ if (p7.isCancel(val) || !val || !val.trim()) break;
3752
3828
  acceptance.push(val.trim());
3753
3829
  }
3754
- const spinner6 = p6.spinner();
3830
+ const spinner6 = p7.spinner();
3755
3831
  spinner6.start("Saving...");
3756
3832
  try {
3757
3833
  const config = await loadConfig(cwd2);
@@ -3761,11 +3837,11 @@ async function runTaskAdd(cwd2) {
3761
3837
  await db.writeFeatureList(cwd2);
3762
3838
  await db.close();
3763
3839
  spinner6.stop("");
3764
- console.log(pc14.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
3765
- console.log(pc14.cyan("\u2192") + " " + pc14.cyan("ahk status") + " to see all tasks");
3840
+ console.log(pc15.green(`\u2713 Task #${task2.id} added \u2014 ${task2.slug} (pending)`));
3841
+ console.log(pc15.cyan("\u2192") + " " + pc15.cyan("ahk status") + " to see all tasks");
3766
3842
  } catch (err) {
3767
- spinner6.stop(pc14.red("Failed"));
3768
- p6.log.error(err instanceof Error ? err.message : String(err));
3843
+ spinner6.stop(pc15.red("Failed"));
3844
+ p7.log.error(err instanceof Error ? err.message : String(err));
3769
3845
  process.exit(1);
3770
3846
  }
3771
3847
  }
@@ -3774,7 +3850,7 @@ async function runTaskAdd(cwd2) {
3774
3850
  import { spawnSync as spawnSync2 } from "child_process";
3775
3851
  import { existsSync as existsSync20 } from "fs";
3776
3852
  import { resolve as resolve11 } from "path";
3777
- import pc15 from "picocolors";
3853
+ import pc16 from "picocolors";
3778
3854
  async function runTaskDone(cwd2, idOrSlug) {
3779
3855
  const config = await loadConfig(cwd2);
3780
3856
  if (config.health.required) {
@@ -3782,7 +3858,7 @@ async function runTaskDone(cwd2, idOrSlug) {
3782
3858
  if (existsSync20(scriptPath)) {
3783
3859
  const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
3784
3860
  if (result.status !== 0) {
3785
- console.error(pc15.red("\u2717 Health check failed \u2014 cannot mark task as done."));
3861
+ console.error(pc16.red("\u2717 Health check failed \u2014 cannot mark task as done."));
3786
3862
  if (result.stdout) console.error(result.stdout);
3787
3863
  if (result.stderr) console.error(result.stderr);
3788
3864
  process.exit(1);
@@ -3795,79 +3871,79 @@ async function runTaskDone(cwd2, idOrSlug) {
3795
3871
  const isId = !isNaN(parsed);
3796
3872
  const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
3797
3873
  if (!task2) {
3798
- console.error(pc15.red(`Task not found: ${idOrSlug}`));
3874
+ console.error(pc16.red(`Task not found: ${idOrSlug}`));
3799
3875
  process.exit(1);
3800
3876
  }
3801
3877
  if (task2.status === "done") {
3802
- console.log(pc15.dim(`Task #${task2.id} is already done.`));
3878
+ console.log(pc16.dim(`Task #${task2.id} is already done.`));
3803
3879
  return;
3804
3880
  }
3805
3881
  await db.updateTaskStatus(task2.id, "done");
3806
3882
  await db.writeFeatureList(cwd2);
3807
- console.log(pc15.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
3883
+ console.log(pc16.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
3808
3884
  } finally {
3809
3885
  await db.close();
3810
3886
  }
3811
3887
  }
3812
3888
 
3813
3889
  // src/commands/task/edit.ts
3814
- import * as p7 from "@clack/prompts";
3815
- import pc16 from "picocolors";
3890
+ import * as p8 from "@clack/prompts";
3891
+ import pc17 from "picocolors";
3816
3892
  async function runTaskEdit(cwd2) {
3817
- p7.intro(pc16.bold("agent-harness-kit \u2014 edit task"));
3893
+ p8.intro(pc17.bold("agent-harness-kit \u2014 edit task"));
3818
3894
  const config = await loadConfig(cwd2);
3819
3895
  const db = await openDB(config, cwd2);
3820
3896
  try {
3821
3897
  const allTasks = await db.getTasks();
3822
3898
  const activeTasks = allTasks.filter((t) => t.status !== "done");
3823
3899
  if (activeTasks.length === 0) {
3824
- p7.log.error("No active tasks to edit.");
3900
+ p8.log.error("No active tasks to edit.");
3825
3901
  return;
3826
3902
  }
3827
- const taskId = await p7.select({
3903
+ const taskId = await p8.select({
3828
3904
  message: "Select a task to edit",
3829
3905
  options: activeTasks.map((t) => ({
3830
3906
  label: `#${t.id} \u2014 ${t.title} (${t.slug})`,
3831
3907
  value: t.id
3832
3908
  }))
3833
3909
  });
3834
- if (p7.isCancel(taskId)) {
3835
- p7.cancel("Cancelled.");
3910
+ if (p8.isCancel(taskId)) {
3911
+ p8.cancel("Cancelled.");
3836
3912
  process.exit(0);
3837
3913
  }
3838
3914
  const task2 = await db.getTaskById(taskId);
3839
3915
  if (!task2) {
3840
- p7.log.error("Task not found");
3916
+ p8.log.error("Task not found");
3841
3917
  process.exit(1);
3842
3918
  }
3843
- const title = await p7.text({
3919
+ const title = await p8.text({
3844
3920
  message: "Title",
3845
3921
  initialValue: task2.title
3846
3922
  });
3847
- if (p7.isCancel(title)) {
3848
- p7.cancel("Cancelled.");
3923
+ if (p8.isCancel(title)) {
3924
+ p8.cancel("Cancelled.");
3849
3925
  process.exit(0);
3850
3926
  }
3851
- const description = await p7.text({
3927
+ const description = await p8.text({
3852
3928
  message: "Description (what and why)",
3853
3929
  initialValue: task2.description ?? ""
3854
3930
  });
3855
- if (p7.isCancel(description)) {
3856
- p7.cancel("Cancelled.");
3931
+ if (p8.isCancel(description)) {
3932
+ p8.cancel("Cancelled.");
3857
3933
  process.exit(0);
3858
3934
  }
3859
3935
  const currentAcceptance = await db.getTaskAcceptance(task2.id);
3860
3936
  const newAcceptance = [];
3861
- p7.log.info("Acceptance criteria \u2014 edit each, empty to delete. Add new ones at the end.");
3937
+ p8.log.info("Acceptance criteria \u2014 edit each, empty to delete. Add new ones at the end.");
3862
3938
  for (let i = 0; i < currentAcceptance.length; i++) {
3863
3939
  const ac = currentAcceptance[i];
3864
- const val = await p7.text({
3940
+ const val = await p8.text({
3865
3941
  message: `#${i + 1}/${currentAcceptance.length}`,
3866
3942
  initialValue: ac.criterion,
3867
3943
  defaultValue: ""
3868
3944
  });
3869
- if (p7.isCancel(val)) {
3870
- p7.cancel("Cancelled.");
3945
+ if (p8.isCancel(val)) {
3946
+ p8.cancel("Cancelled.");
3871
3947
  process.exit(0);
3872
3948
  }
3873
3949
  const trimmed = val.trim();
@@ -3876,11 +3952,11 @@ async function runTaskEdit(cwd2) {
3876
3952
  }
3877
3953
  }
3878
3954
  while (true) {
3879
- const val = await p7.text({ message: "New acceptance criterion", placeholder: "(press Enter to finish)" });
3880
- if (p7.isCancel(val) || !val || !val.trim()) break;
3955
+ const val = await p8.text({ message: "New acceptance criterion", placeholder: "(press Enter to finish)" });
3956
+ if (p8.isCancel(val) || !val || !val.trim()) break;
3881
3957
  newAcceptance.push(val.trim());
3882
3958
  }
3883
- const spinner6 = p7.spinner();
3959
+ const spinner6 = p8.spinner();
3884
3960
  spinner6.start("Saving...");
3885
3961
  try {
3886
3962
  const newSlug = slugify(title);
@@ -3892,10 +3968,10 @@ async function runTaskEdit(cwd2) {
3892
3968
  await db.updateTaskAcceptance(task2.id, newAcceptance);
3893
3969
  await db.writeFeatureList(cwd2);
3894
3970
  spinner6.stop("");
3895
- console.log(pc16.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
3971
+ console.log(pc17.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
3896
3972
  } catch (err) {
3897
- spinner6.stop(pc16.red("Failed"));
3898
- p7.log.error(err instanceof Error ? err.message : String(err));
3973
+ spinner6.stop(pc17.red("Failed"));
3974
+ p8.log.error(err instanceof Error ? err.message : String(err));
3899
3975
  process.exit(1);
3900
3976
  }
3901
3977
  } finally {
@@ -3905,12 +3981,12 @@ async function runTaskEdit(cwd2) {
3905
3981
 
3906
3982
  // src/commands/task/list.ts
3907
3983
  import Table2 from "cli-table3";
3908
- import pc17 from "picocolors";
3984
+ import pc18 from "picocolors";
3909
3985
  var STATUS_COLOR2 = {
3910
- pending: (s) => pc17.dim(s),
3911
- in_progress: (s) => pc17.cyan(s),
3912
- done: (s) => pc17.green(s),
3913
- blocked: (s) => pc17.red(s)
3986
+ pending: (s) => pc18.dim(s),
3987
+ in_progress: (s) => pc18.cyan(s),
3988
+ done: (s) => pc18.green(s),
3989
+ blocked: (s) => pc18.red(s)
3914
3990
  };
3915
3991
  async function runTaskList(cwd2, opts) {
3916
3992
  const config = await loadConfig(cwd2);
@@ -3927,11 +4003,11 @@ async function runTaskList(cwd2, opts) {
3927
4003
  let msg = "No tasks";
3928
4004
  if (filterStatus) msg += ` with status: ${filterStatus}`;
3929
4005
  if (opts.archived) msg += " (archived)";
3930
- console.log(pc17.dim(msg + "."));
4006
+ console.log(pc18.dim(msg + "."));
3931
4007
  return;
3932
4008
  }
3933
4009
  const table = new Table2({
3934
- head: ["ID", "Slug", "Title", "Status"].map((h) => pc17.bold(h)),
4010
+ head: ["ID", "Slug", "Title", "Status"].map((h) => pc18.bold(h)),
3935
4011
  style: { head: [], border: [] }
3936
4012
  });
3937
4013
  for (const t of tasks) {
@@ -3942,7 +4018,7 @@ async function runTaskList(cwd2, opts) {
3942
4018
  if (!opts.archived && !opts.includeArchived) {
3943
4019
  const archivedTasks = await db.getArchivedTasks();
3944
4020
  if (archivedTasks.length > 0) {
3945
- console.log(pc17.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
4021
+ console.log(pc18.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
3946
4022
  }
3947
4023
  }
3948
4024
  } finally {
@@ -3952,8 +4028,8 @@ async function runTaskList(cwd2, opts) {
3952
4028
 
3953
4029
  // src/core/path-probe.ts
3954
4030
  import { accessSync, constants, readdirSync as readdirSync3 } from "fs";
3955
- import { join as join22 } from "path";
3956
- import pc18 from "picocolors";
4031
+ import { join as join23 } from "path";
4032
+ import pc19 from "picocolors";
3957
4033
  var DEFAULT_PATHEXT = [".COM", ".EXE", ".BAT", ".CMD"];
3958
4034
  function defaultIsExecutable(filePath) {
3959
4035
  try {
@@ -3997,7 +4073,7 @@ function resolveOnPath(name, options = {}) {
3997
4073
  }
3998
4074
  for (const dir of dirs) {
3999
4075
  try {
4000
- if (isExecutable(join22(dir, name))) return true;
4076
+ if (isExecutable(join23(dir, name))) return true;
4001
4077
  } catch {
4002
4078
  continue;
4003
4079
  }
@@ -4008,16 +4084,16 @@ function isExecutableOnPath(name) {
4008
4084
  return resolveOnPath(name);
4009
4085
  }
4010
4086
  function printMissingGlobalBinaryWarning() {
4011
- console.error(pc18.yellow("\u26A0 `ahk` was not found on your PATH."));
4012
- console.error(pc18.dim(" Your project has no local install, so the generated MCP config launches"));
4013
- console.error(pc18.dim(" `ahk serve` directly. Without `ahk` on your PATH, starting the MCP server"));
4014
- console.error(pc18.dim(" from that config will fail. This is only a warning \u2014 the command continues."));
4015
- console.error(pc18.dim(` Run: npm i -g ${pkg.name} (install globally)`));
4016
- console.error(pc18.dim(` or: npm install --save-dev ${pkg.name} (install locally in this project)`));
4087
+ console.error(pc19.yellow("\u26A0 `ahk` was not found on your PATH."));
4088
+ console.error(pc19.dim(" Your project has no local install, so the generated MCP config launches"));
4089
+ console.error(pc19.dim(" `ahk serve` directly. Without `ahk` on your PATH, starting the MCP server"));
4090
+ console.error(pc19.dim(" from that config will fail. This is only a warning \u2014 the command continues."));
4091
+ console.error(pc19.dim(` Run: npm i -g ${pkg.name} (install globally)`));
4092
+ console.error(pc19.dim(` or: npm install --save-dev ${pkg.name} (install locally in this project)`));
4017
4093
  }
4018
4094
 
4019
4095
  // src/core/update-check.ts
4020
- import pc19 from "picocolors";
4096
+ import pc20 from "picocolors";
4021
4097
  var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
4022
4098
  var TIMEOUT_MS2 = 2500;
4023
4099
  function checkForUpdate(currentVersion) {
@@ -4035,8 +4111,8 @@ function checkForUpdate(currentVersion) {
4035
4111
  }
4036
4112
  function printUpdateMessage({ current, latest }) {
4037
4113
  const lines = [
4038
- ` Update available ${pc19.dim(current)} \u2192 ${pc19.green(latest)} `,
4039
- ` Run: ${pc19.cyan(`pnpm i ${pkg.name}@${latest}`)} `
4114
+ ` Update available ${pc20.dim(current)} \u2192 ${pc20.green(latest)} `,
4115
+ ` Run: ${pc20.cyan(`pnpm i ${pkg.name}@${latest}`)} `
4040
4116
  ];
4041
4117
  drawBox(lines);
4042
4118
  }
@@ -4113,7 +4189,7 @@ migrate.command("storage").description(
4113
4189
  try {
4114
4190
  await runMigrateStorage(cwd, { force: opts.force, dryRun: opts["dry-run"] });
4115
4191
  } catch (err) {
4116
- console.error(pc20.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
4192
+ console.error(pc21.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
4117
4193
  process.exit(1);
4118
4194
  }
4119
4195
  });
@@ -4126,6 +4202,9 @@ program.command("reset").description("Reset/clear harness data (DB, feature list
4126
4202
  program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
4127
4203
  await runDoctor(cwd);
4128
4204
  });
4205
+ program.command("models").description("Re-prompt per-role Claude Code models and regenerate .claude/agents/*.md (claude-code projects only)").action(async () => {
4206
+ await runModels(cwd);
4207
+ });
4129
4208
  program.hook("preAction", () => {
4130
4209
  if (!isLocalInstallSatisfied(cwd)) {
4131
4210
  printLocalInstallWarning();