@cardor/agent-harness-kit 2.0.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/README.md +85 -13
- package/dist/cli.js +530 -340
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
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
|
|
19
|
+
import pc21 from "picocolors";
|
|
20
20
|
|
|
21
21
|
// src/commands/build.ts
|
|
22
22
|
import { watch } from "fs";
|
|
23
|
-
import * as
|
|
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((
|
|
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
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 (!
|
|
143
|
+
if (!hasRealLocalInstall(cwd2)) {
|
|
136
144
|
return ["ahk", "serve", "--port", portStr];
|
|
137
145
|
}
|
|
138
146
|
switch (pm) {
|
|
@@ -380,6 +388,17 @@ function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
|
380
388
|
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
381
389
|
writeFileSync2(filePath, content, "utf8");
|
|
382
390
|
}
|
|
391
|
+
function mergeGrokConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
392
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
393
|
+
let content = "";
|
|
394
|
+
if (existsSync4(filePath)) {
|
|
395
|
+
content = readFileSync3(filePath, "utf8");
|
|
396
|
+
}
|
|
397
|
+
const [command, ...args] = getMcpCommandParts(pm, port, cwd2);
|
|
398
|
+
const sectionBody = [`command = ${JSON.stringify(command)}`, `args = ${JSON.stringify(args)}`].join("\n");
|
|
399
|
+
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
400
|
+
writeFileSync2(filePath, content, "utf8");
|
|
401
|
+
}
|
|
383
402
|
|
|
384
403
|
// src/core/materializer/scaffold-utils.ts
|
|
385
404
|
import { createHash } from "crypto";
|
|
@@ -420,6 +439,9 @@ These tools may still appear available to you. The sandbox will reject the call.
|
|
|
420
439
|
function codexRestrictionNotice(agentName) {
|
|
421
440
|
return restrictionFor(agentName) === "no-write" ? CODEX_READ_ONLY_NOTICE : "";
|
|
422
441
|
}
|
|
442
|
+
function grokToolsAllowlist(agentName) {
|
|
443
|
+
return restrictionFor(agentName) === "no-write" ? ["Bash", "Read", "NotebookRead", "Grep", "Glob", "WebFetch", "WebSearch", "search_tool", "use_tool"] : [];
|
|
444
|
+
}
|
|
423
445
|
|
|
424
446
|
// src/core/materializer/templates.ts
|
|
425
447
|
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -849,6 +871,11 @@ function translateFrontmatterForOpenCode(md, agentName) {
|
|
|
849
871
|
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
850
872
|
return appendFrontmatterMapping(result, "permission", opencodePermissions(agentName));
|
|
851
873
|
}
|
|
874
|
+
function translateFrontmatterForGrok(md, agentName) {
|
|
875
|
+
let result = stripFrontmatterBlockSequence(md, "tools");
|
|
876
|
+
result = stripFrontmatterBlockSequence(result, "disallowedTools");
|
|
877
|
+
return appendFrontmatterBlockSequence(result, "tools", grokToolsAllowlist(agentName));
|
|
878
|
+
}
|
|
852
879
|
var GITIGNORE_ENTRIES = `
|
|
853
880
|
# agent-harness-kit
|
|
854
881
|
.harness/harness.db
|
|
@@ -1064,7 +1091,7 @@ No tasks in progress.
|
|
|
1064
1091
|
],
|
|
1065
1092
|
{ force: opts.force, backupRoot: join7(cwd2, config.storage.dir, "backups") }
|
|
1066
1093
|
);
|
|
1067
|
-
const agents = writeAgentFiles(cwd2, claudeAgentFiles(config), {
|
|
1094
|
+
const agents = writeAgentFiles(cwd2, claudeAgentFiles(config, opts.claudeAgentModels), {
|
|
1068
1095
|
force: opts.force,
|
|
1069
1096
|
backupRoot: join7(cwd2, config.storage.dir, "backups")
|
|
1070
1097
|
});
|
|
@@ -1163,9 +1190,73 @@ No tasks in progress.
|
|
|
1163
1190
|
}
|
|
1164
1191
|
};
|
|
1165
1192
|
|
|
1166
|
-
// src/core/materializer/
|
|
1193
|
+
// src/core/materializer/grok.ts
|
|
1167
1194
|
import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
1168
1195
|
import { join as join9, resolve as resolve4 } from "path";
|
|
1196
|
+
function grokAgentFiles(config) {
|
|
1197
|
+
const projectName = config.project.name;
|
|
1198
|
+
return [
|
|
1199
|
+
{ relPath: ".grok/agents/lead.md", content: translateFrontmatterForGrok(agentLead({ projectName }), "lead") },
|
|
1200
|
+
{ relPath: ".grok/agents/explorer.md", content: translateFrontmatterForGrok(agentExplorer({ projectName }), "explorer") },
|
|
1201
|
+
{ relPath: ".grok/agents/consultant.md", content: translateFrontmatterForGrok(agentConsultant({ projectName }), "consultant") },
|
|
1202
|
+
{ relPath: ".grok/agents/builder.md", content: translateFrontmatterForGrok(agentBuilder({ projectName }), "builder") },
|
|
1203
|
+
{ relPath: ".grok/agents/reviewer.md", content: translateFrontmatterForGrok(agentReviewer({ projectName }), "reviewer") }
|
|
1204
|
+
];
|
|
1205
|
+
}
|
|
1206
|
+
var GrokMaterializer = class {
|
|
1207
|
+
async scaffold(config, opts) {
|
|
1208
|
+
const { cwd: cwd2 } = opts;
|
|
1209
|
+
const write2 = (relPath, content, mode) => {
|
|
1210
|
+
const abs = join9(cwd2, relPath);
|
|
1211
|
+
mkdirSync5(resolve4(abs, ".."), { recursive: true });
|
|
1212
|
+
writeFileSync5(abs, content, { encoding: "utf8", mode });
|
|
1213
|
+
};
|
|
1214
|
+
write2("AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1215
|
+
if (!existsSync8(join9(cwd2, "health.sh"))) {
|
|
1216
|
+
write2("health.sh", HEALTH_SH, 493);
|
|
1217
|
+
}
|
|
1218
|
+
if (config.storage.scope === "local" && !existsSync8(join9(cwd2, config.storage.markdownFallback.path))) {
|
|
1219
|
+
write2(
|
|
1220
|
+
config.storage.markdownFallback.path,
|
|
1221
|
+
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
1222
|
+
<!-- Run ahk status to refresh -->
|
|
1223
|
+
|
|
1224
|
+
# Current Session
|
|
1225
|
+
|
|
1226
|
+
No tasks in progress.
|
|
1227
|
+
`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
writeAgentFiles(cwd2, grokAgentFiles(config));
|
|
1231
|
+
mergeGrokConfigToml(join9(cwd2, ".grok/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1232
|
+
appendGitignore(cwd2);
|
|
1233
|
+
writeSkills(cwd2, ".grok/skills");
|
|
1234
|
+
}
|
|
1235
|
+
async build(config, cwd2, opts = {}) {
|
|
1236
|
+
const derived = reconcileGeneratedFiles(
|
|
1237
|
+
cwd2,
|
|
1238
|
+
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1239
|
+
{ force: opts.force, backupRoot: join9(cwd2, config.storage.dir, "backups") }
|
|
1240
|
+
);
|
|
1241
|
+
const agents = writeAgentFiles(cwd2, grokAgentFiles(config), {
|
|
1242
|
+
force: opts.force,
|
|
1243
|
+
backupRoot: join9(cwd2, config.storage.dir, "backups")
|
|
1244
|
+
});
|
|
1245
|
+
mergeGrokConfigToml(join9(cwd2, ".grok/config.toml"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1246
|
+
writeSkills(cwd2, ".grok/skills");
|
|
1247
|
+
return { agents, derived };
|
|
1248
|
+
}
|
|
1249
|
+
async migrate(config, _to, _cwd) {
|
|
1250
|
+
void config;
|
|
1251
|
+
}
|
|
1252
|
+
async syncPermissions(_cwd) {
|
|
1253
|
+
console.log(" Permissions sync not needed for grok-cli \u2014 skipping");
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
// src/core/materializer/opencode.ts
|
|
1258
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
1259
|
+
import { join as join10, resolve as resolve5 } from "path";
|
|
1169
1260
|
function opencodeAgentFiles(config) {
|
|
1170
1261
|
const projectName = config.project.name;
|
|
1171
1262
|
return [
|
|
@@ -1180,15 +1271,15 @@ var OpenCodeMaterializer = class {
|
|
|
1180
1271
|
async scaffold(config, opts) {
|
|
1181
1272
|
const { cwd: cwd2 } = opts;
|
|
1182
1273
|
const write2 = (relPath, content, mode) => {
|
|
1183
|
-
const abs =
|
|
1184
|
-
|
|
1185
|
-
|
|
1274
|
+
const abs = join10(cwd2, relPath);
|
|
1275
|
+
mkdirSync6(resolve5(abs, ".."), { recursive: true });
|
|
1276
|
+
writeFileSync6(abs, content, { encoding: "utf8", mode });
|
|
1186
1277
|
};
|
|
1187
1278
|
write2("AGENTS.md", stampGenerated(agentsMd(config)));
|
|
1188
|
-
if (!
|
|
1279
|
+
if (!existsSync9(join10(cwd2, "health.sh"))) {
|
|
1189
1280
|
write2("health.sh", HEALTH_SH, 493);
|
|
1190
1281
|
}
|
|
1191
|
-
if (config.storage.scope === "local" && !
|
|
1282
|
+
if (config.storage.scope === "local" && !existsSync9(join10(cwd2, config.storage.markdownFallback.path))) {
|
|
1192
1283
|
write2(
|
|
1193
1284
|
config.storage.markdownFallback.path,
|
|
1194
1285
|
`<!-- AUTO-GENERATED by agent-harness-kit \u2014 DO NOT EDIT MANUALLY -->
|
|
@@ -1201,7 +1292,7 @@ No tasks in progress.
|
|
|
1201
1292
|
);
|
|
1202
1293
|
}
|
|
1203
1294
|
writeAgentFiles(cwd2, opencodeAgentFiles(config));
|
|
1204
|
-
mergeOpencodeJson(
|
|
1295
|
+
mergeOpencodeJson(join10(cwd2, "opencode.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1205
1296
|
appendGitignore(cwd2);
|
|
1206
1297
|
writeSkills(cwd2, ".opencode/skills");
|
|
1207
1298
|
}
|
|
@@ -1209,13 +1300,13 @@ No tasks in progress.
|
|
|
1209
1300
|
const derived = reconcileGeneratedFiles(
|
|
1210
1301
|
cwd2,
|
|
1211
1302
|
[{ relPath: "AGENTS.md", content: agentsMd(config) }],
|
|
1212
|
-
{ force: opts.force, backupRoot:
|
|
1303
|
+
{ force: opts.force, backupRoot: join10(cwd2, config.storage.dir, "backups") }
|
|
1213
1304
|
);
|
|
1214
1305
|
const agents = writeAgentFiles(cwd2, opencodeAgentFiles(config), {
|
|
1215
1306
|
force: opts.force,
|
|
1216
|
-
backupRoot:
|
|
1307
|
+
backupRoot: join10(cwd2, config.storage.dir, "backups")
|
|
1217
1308
|
});
|
|
1218
|
-
mergeOpencodeJson(
|
|
1309
|
+
mergeOpencodeJson(join10(cwd2, "opencode.json"), config.tools.mcp.port, cwd2, detectPackageManager(cwd2));
|
|
1219
1310
|
writeSkills(cwd2, ".opencode/skills");
|
|
1220
1311
|
return { agents, derived };
|
|
1221
1312
|
}
|
|
@@ -1236,25 +1327,60 @@ function getMaterializer(provider) {
|
|
|
1236
1327
|
return new OpenCodeMaterializer();
|
|
1237
1328
|
case "codex-cli":
|
|
1238
1329
|
return new CodexCliMaterializer();
|
|
1330
|
+
case "grok-cli":
|
|
1331
|
+
return new GrokMaterializer();
|
|
1239
1332
|
default:
|
|
1240
1333
|
throw new Error(`Unknown provider: ${provider}`);
|
|
1241
1334
|
}
|
|
1242
1335
|
}
|
|
1243
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
|
+
|
|
1244
1370
|
// src/commands/build.ts
|
|
1245
1371
|
async function runBuild(cwd2, opts) {
|
|
1246
1372
|
await buildOnce(cwd2, opts.force);
|
|
1247
1373
|
if (opts.sync) {
|
|
1248
|
-
|
|
1374
|
+
p2.log.step("Syncing agent permissions...");
|
|
1249
1375
|
const config = await loadConfig(cwd2);
|
|
1250
1376
|
const materializer = getMaterializer(config.provider);
|
|
1251
1377
|
await materializer.syncPermissions(cwd2);
|
|
1252
1378
|
}
|
|
1253
1379
|
if (opts.watch) {
|
|
1254
|
-
|
|
1380
|
+
p2.log.info(`Watching agent-harness-kit.config.ts for changes...`);
|
|
1255
1381
|
watch(cwd2, { recursive: false }, async (_, filename) => {
|
|
1256
1382
|
if (filename?.startsWith("agent-harness-kit.config")) {
|
|
1257
|
-
|
|
1383
|
+
p2.log.step("Config changed \u2014 rebuilding...");
|
|
1258
1384
|
await buildOnce(cwd2, false);
|
|
1259
1385
|
}
|
|
1260
1386
|
});
|
|
@@ -1263,36 +1389,45 @@ async function runBuild(cwd2, opts) {
|
|
|
1263
1389
|
}
|
|
1264
1390
|
}
|
|
1265
1391
|
async function buildOnce(cwd2, force) {
|
|
1266
|
-
|
|
1267
|
-
|
|
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...");
|
|
1268
1405
|
try {
|
|
1269
|
-
const config = await loadConfig(cwd2);
|
|
1270
|
-
spinner6.message("Rebuilding files...");
|
|
1271
1406
|
const materializer = getMaterializer(config.provider);
|
|
1272
|
-
const report = await materializer.build(config, cwd2, { force });
|
|
1407
|
+
const report = await materializer.build(config, cwd2, { force, claudeAgentModels });
|
|
1273
1408
|
spinner6.stop(pc2.green("Build complete"));
|
|
1274
1409
|
const d = report.derived;
|
|
1275
1410
|
const upToDate = [...d.created, ...d.current, ...d.propagated];
|
|
1276
1411
|
if (upToDate.length > 0) {
|
|
1277
|
-
|
|
1412
|
+
p2.log.success(upToDate.join(", "));
|
|
1278
1413
|
}
|
|
1279
1414
|
if (d.propagated.length > 0) {
|
|
1280
|
-
|
|
1415
|
+
p2.log.info(`Propagated config changes to ${d.propagated.length} generated file(s):
|
|
1281
1416
|
${d.propagated.join("\n ")}`);
|
|
1282
1417
|
}
|
|
1283
1418
|
if (d.overwritten.length > 0) {
|
|
1284
|
-
|
|
1419
|
+
p2.log.warn(
|
|
1285
1420
|
pc2.yellow(
|
|
1286
1421
|
`--force REGENERATED ${d.overwritten.length} hand-edited generated file(s), discarding your edits:
|
|
1287
1422
|
` + d.overwritten.join("\n ")
|
|
1288
1423
|
)
|
|
1289
1424
|
);
|
|
1290
1425
|
if (d.backupDir) {
|
|
1291
|
-
|
|
1426
|
+
p2.log.info(pc2.yellow(` Previous content backed up \u2192 ${d.backupDir}`));
|
|
1292
1427
|
}
|
|
1293
1428
|
}
|
|
1294
1429
|
if (d.preserved.length > 0) {
|
|
1295
|
-
|
|
1430
|
+
p2.log.warn(
|
|
1296
1431
|
pc2.yellow(
|
|
1297
1432
|
`Left ${d.preserved.length} hand-edited generated file(s) UNTOUCHED \u2014 your edits are safe:
|
|
1298
1433
|
` + d.preserved.join("\n ") + `
|
|
@@ -1301,26 +1436,26 @@ async function buildOnce(cwd2, force) {
|
|
|
1301
1436
|
)
|
|
1302
1437
|
);
|
|
1303
1438
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1439
|
+
p2.log.success(`Agent definitions (${config.provider})`);
|
|
1440
|
+
p2.log.success("MCP config");
|
|
1306
1441
|
const { created, overwritten, preserved, backupDir } = report.agents;
|
|
1307
1442
|
if (created.length > 0) {
|
|
1308
|
-
|
|
1443
|
+
p2.log.info(`Created ${created.length} missing agent file(s):
|
|
1309
1444
|
${created.join("\n ")}`);
|
|
1310
1445
|
}
|
|
1311
1446
|
if (overwritten.length > 0) {
|
|
1312
|
-
|
|
1447
|
+
p2.log.warn(
|
|
1313
1448
|
pc2.yellow(
|
|
1314
1449
|
`--force REGENERATED ${overwritten.length} existing agent file(s), discarding any customizations:
|
|
1315
1450
|
` + overwritten.join("\n ")
|
|
1316
1451
|
)
|
|
1317
1452
|
);
|
|
1318
1453
|
if (backupDir) {
|
|
1319
|
-
|
|
1454
|
+
p2.log.info(pc2.yellow(` Previous content backed up \u2192 ${backupDir}`));
|
|
1320
1455
|
}
|
|
1321
1456
|
}
|
|
1322
1457
|
if (preserved.length > 0) {
|
|
1323
|
-
|
|
1458
|
+
p2.log.info(
|
|
1324
1459
|
`Left ${preserved.length} existing agent file(s) untouched \u2014 agent files are yours to edit.
|
|
1325
1460
|
Re-run with --force to regenerate them from the packaged templates (this DESTROYS your edits;
|
|
1326
1461
|
a backup is written first).`
|
|
@@ -1328,21 +1463,21 @@ async function buildOnce(cwd2, force) {
|
|
|
1328
1463
|
}
|
|
1329
1464
|
} catch (err) {
|
|
1330
1465
|
spinner6.stop(pc2.red("Build failed"));
|
|
1331
|
-
|
|
1466
|
+
p2.log.error(err instanceof Error ? err.message : String(err));
|
|
1332
1467
|
process.exit(1);
|
|
1333
1468
|
}
|
|
1334
1469
|
}
|
|
1335
1470
|
|
|
1336
1471
|
// src/commands/dashboard.ts
|
|
1337
1472
|
import { homedir } from "os";
|
|
1338
|
-
import { dirname as dirname5, join as
|
|
1473
|
+
import { dirname as dirname5, join as join12 } from "path";
|
|
1339
1474
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1340
1475
|
import pc3 from "picocolors";
|
|
1341
1476
|
|
|
1342
1477
|
// src/core/dashboard-server.ts
|
|
1343
1478
|
import { watch as watch2 } from "fs";
|
|
1344
|
-
import { existsSync as
|
|
1345
|
-
import { extname, join as
|
|
1479
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
1480
|
+
import { extname, join as join11 } from "path";
|
|
1346
1481
|
import { serve } from "@hono/node-server";
|
|
1347
1482
|
import { Hono } from "hono";
|
|
1348
1483
|
import { WebSocketServer } from "ws";
|
|
@@ -1351,11 +1486,11 @@ import { WebSocketServer } from "ws";
|
|
|
1351
1486
|
import { createServer } from "net";
|
|
1352
1487
|
var DASHBOARD_BIND_HOST = void 0;
|
|
1353
1488
|
function isPortFree(port, host = DASHBOARD_BIND_HOST) {
|
|
1354
|
-
return new Promise((
|
|
1489
|
+
return new Promise((resolve12) => {
|
|
1355
1490
|
const server = createServer();
|
|
1356
|
-
server.once("error", () =>
|
|
1491
|
+
server.once("error", () => resolve12(false));
|
|
1357
1492
|
server.once("listening", () => {
|
|
1358
|
-
server.close(() =>
|
|
1493
|
+
server.close(() => resolve12(true));
|
|
1359
1494
|
});
|
|
1360
1495
|
server.listen(port, host);
|
|
1361
1496
|
});
|
|
@@ -1398,7 +1533,7 @@ function fileResponse(filePath) {
|
|
|
1398
1533
|
});
|
|
1399
1534
|
}
|
|
1400
1535
|
function awaitServerListening(server, port) {
|
|
1401
|
-
return new Promise((
|
|
1536
|
+
return new Promise((resolve12, reject) => {
|
|
1402
1537
|
const closeQuietly = () => {
|
|
1403
1538
|
try {
|
|
1404
1539
|
server.close(() => {
|
|
@@ -1421,7 +1556,7 @@ function awaitServerListening(server, port) {
|
|
|
1421
1556
|
};
|
|
1422
1557
|
const onListening = () => {
|
|
1423
1558
|
server.off("error", onError);
|
|
1424
|
-
|
|
1559
|
+
resolve12();
|
|
1425
1560
|
};
|
|
1426
1561
|
server.once("error", onError);
|
|
1427
1562
|
server.once("listening", onListening);
|
|
@@ -1527,15 +1662,15 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1527
1662
|
app.get("/*", (c) => {
|
|
1528
1663
|
const urlPath = c.req.path;
|
|
1529
1664
|
if (urlPath !== "/") {
|
|
1530
|
-
const candidate =
|
|
1531
|
-
if (
|
|
1665
|
+
const candidate = join11(staticPath, urlPath);
|
|
1666
|
+
if (existsSync10(candidate)) {
|
|
1532
1667
|
try {
|
|
1533
1668
|
return fileResponse(candidate);
|
|
1534
1669
|
} catch {
|
|
1535
1670
|
}
|
|
1536
1671
|
}
|
|
1537
1672
|
}
|
|
1538
|
-
return fileResponse(
|
|
1673
|
+
return fileResponse(join11(staticPath, "index.html"));
|
|
1539
1674
|
});
|
|
1540
1675
|
const resolvedPort = await findFreePort(port, { host: DASHBOARD_BIND_HOST });
|
|
1541
1676
|
if (resolvedPort !== port) {
|
|
@@ -1574,7 +1709,7 @@ async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
1574
1709
|
let watcher = null;
|
|
1575
1710
|
if (dbPath) {
|
|
1576
1711
|
const walPath = `${dbPath}-wal`;
|
|
1577
|
-
const watchTarget =
|
|
1712
|
+
const watchTarget = existsSync10(walPath) ? walPath : dbPath;
|
|
1578
1713
|
watcher = watch2(watchTarget, broadcast);
|
|
1579
1714
|
}
|
|
1580
1715
|
return {
|
|
@@ -1594,7 +1729,7 @@ async function runDashboard(cwd2, opts) {
|
|
|
1594
1729
|
const config = await loadConfig(cwd2);
|
|
1595
1730
|
const db = await openDB(config, cwd2);
|
|
1596
1731
|
const dbPath = config.database.type === "sqlite" ? resolveSqlitePath(config, cwd2, homedir()) : null;
|
|
1597
|
-
const staticPath =
|
|
1732
|
+
const staticPath = join12(__dirname3, "dashboard-dist");
|
|
1598
1733
|
const { url } = await startDashboardServer(db, dbPath, staticPath, opts.port);
|
|
1599
1734
|
console.log(pc3.green(`\u2713`) + ` Dashboard running at ${pc3.bold(pc3.cyan(url))}`);
|
|
1600
1735
|
console.log(pc3.dim(` WebSocket live updates enabled`));
|
|
@@ -1614,8 +1749,8 @@ async function runDashboard(cwd2, opts) {
|
|
|
1614
1749
|
import pc4 from "picocolors";
|
|
1615
1750
|
|
|
1616
1751
|
// src/core/doctor.ts
|
|
1617
|
-
import { existsSync as
|
|
1618
|
-
import { dirname as dirname6, join as
|
|
1752
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
1753
|
+
import { dirname as dirname6, join as join13 } from "path";
|
|
1619
1754
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1620
1755
|
var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
1621
1756
|
var TIMEOUT_MS = 2e3;
|
|
@@ -1664,19 +1799,21 @@ function getProviderAgentInfo(provider) {
|
|
|
1664
1799
|
return { agentsDir: ".opencode/agents", ext: ".md" };
|
|
1665
1800
|
case "codex-cli":
|
|
1666
1801
|
return { agentsDir: ".codex/agents", ext: ".toml" };
|
|
1802
|
+
case "grok-cli":
|
|
1803
|
+
return { agentsDir: ".grok/agents", ext: ".md" };
|
|
1667
1804
|
default:
|
|
1668
1805
|
return { agentsDir: ".claude/agents", ext: ".md" };
|
|
1669
1806
|
}
|
|
1670
1807
|
}
|
|
1671
1808
|
function checkAgentFilesAtRoot(agentsRoot, ext) {
|
|
1672
1809
|
return AGENT_NAMES.map((name) => {
|
|
1673
|
-
const filePath =
|
|
1674
|
-
return { name, status:
|
|
1810
|
+
const filePath = join13(agentsRoot, `${name}${ext}`);
|
|
1811
|
+
return { name, status: existsSync11(filePath) ? "ok" : "missing" };
|
|
1675
1812
|
});
|
|
1676
1813
|
}
|
|
1677
1814
|
function checkAgentFiles(cwd2, provider) {
|
|
1678
1815
|
const { agentsDir, ext } = getProviderAgentInfo(provider);
|
|
1679
|
-
return checkAgentFilesAtRoot(
|
|
1816
|
+
return checkAgentFilesAtRoot(join13(cwd2, agentsDir), ext);
|
|
1680
1817
|
}
|
|
1681
1818
|
function getProviderSkillsDir(provider) {
|
|
1682
1819
|
switch (provider) {
|
|
@@ -1686,16 +1823,18 @@ function getProviderSkillsDir(provider) {
|
|
|
1686
1823
|
return ".opencode/skills";
|
|
1687
1824
|
case "codex-cli":
|
|
1688
1825
|
return ".agents/skills";
|
|
1826
|
+
case "grok-cli":
|
|
1827
|
+
return ".grok/skills";
|
|
1689
1828
|
default:
|
|
1690
1829
|
return ".claude/skills";
|
|
1691
1830
|
}
|
|
1692
1831
|
}
|
|
1693
1832
|
function checkSkillsAtRoot(skillsRoot) {
|
|
1694
|
-
const skillSourceBase =
|
|
1833
|
+
const skillSourceBase = join13(__dirname4, "skills");
|
|
1695
1834
|
return SKILL_NAMES.map((name) => {
|
|
1696
|
-
const livePath =
|
|
1697
|
-
const sourcePath =
|
|
1698
|
-
if (!
|
|
1835
|
+
const livePath = join13(skillsRoot, name, "SKILL.md");
|
|
1836
|
+
const sourcePath = join13(skillSourceBase, name, "SKILL.md");
|
|
1837
|
+
if (!existsSync11(livePath)) {
|
|
1699
1838
|
return { name, status: "missing" };
|
|
1700
1839
|
}
|
|
1701
1840
|
try {
|
|
@@ -1709,7 +1848,7 @@ function checkSkillsAtRoot(skillsRoot) {
|
|
|
1709
1848
|
}
|
|
1710
1849
|
function checkSkills(cwd2, provider) {
|
|
1711
1850
|
const skillsDir = getProviderSkillsDir(provider);
|
|
1712
|
-
return checkSkillsAtRoot(
|
|
1851
|
+
return checkSkillsAtRoot(join13(cwd2, skillsDir));
|
|
1713
1852
|
}
|
|
1714
1853
|
async function getDoctorStatus(cwd2) {
|
|
1715
1854
|
const lib = await checkLibVersion();
|
|
@@ -1810,7 +1949,7 @@ async function runDoctor(cwd2) {
|
|
|
1810
1949
|
}
|
|
1811
1950
|
|
|
1812
1951
|
// src/commands/export.ts
|
|
1813
|
-
import { writeFileSync as
|
|
1952
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
1814
1953
|
import pc5 from "picocolors";
|
|
1815
1954
|
async function runExport(cwd2, opts) {
|
|
1816
1955
|
if (!opts.sql && !opts.json) {
|
|
@@ -1824,7 +1963,7 @@ async function runExport(cwd2, opts) {
|
|
|
1824
1963
|
const data = await db.exportJson();
|
|
1825
1964
|
const out = JSON.stringify(data, null, 2) + "\n";
|
|
1826
1965
|
if (opts.output) {
|
|
1827
|
-
|
|
1966
|
+
writeFileSync7(opts.output, out, "utf8");
|
|
1828
1967
|
console.log(pc5.green(`\u2713 Exported JSON \u2192 ${opts.output}`));
|
|
1829
1968
|
} else {
|
|
1830
1969
|
process.stdout.write(out);
|
|
@@ -1841,9 +1980,9 @@ async function runExport(cwd2, opts) {
|
|
|
1841
1980
|
|
|
1842
1981
|
// src/commands/health.ts
|
|
1843
1982
|
import { spawnSync } from "child_process";
|
|
1844
|
-
import { existsSync as
|
|
1983
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1845
1984
|
import { homedir as homedir2 } from "os";
|
|
1846
|
-
import { join as
|
|
1985
|
+
import { join as join14, resolve as resolve6 } from "path";
|
|
1847
1986
|
import pc6 from "picocolors";
|
|
1848
1987
|
function checkLine(label, ok3, message, indent = 0) {
|
|
1849
1988
|
const prefix = label ? pc6.cyan(`[${label}] `) : " ".repeat(indent);
|
|
@@ -1862,7 +2001,7 @@ async function runHealth(cwd2) {
|
|
|
1862
2001
|
let dbOk;
|
|
1863
2002
|
if (config.database.type === "sqlite") {
|
|
1864
2003
|
const dbPath = resolveSqlitePath(config, cwd2, homedir2());
|
|
1865
|
-
dbOk =
|
|
2004
|
+
dbOk = existsSync12(dbPath);
|
|
1866
2005
|
checkLine("checking DB", dbOk, `${dbPath} reachable`);
|
|
1867
2006
|
} else {
|
|
1868
2007
|
dbOk = true;
|
|
@@ -1875,8 +2014,8 @@ async function runHealth(cwd2) {
|
|
|
1875
2014
|
const agentsLabelWidth = "[checking agents] ".length;
|
|
1876
2015
|
for (let i = 0; i < agentNames.length; i++) {
|
|
1877
2016
|
const name = agentNames[i];
|
|
1878
|
-
const agentPath =
|
|
1879
|
-
const ok3 =
|
|
2017
|
+
const agentPath = join14(cwd2, agentsDir, `${name}${providerFiles.agentExtension}`);
|
|
2018
|
+
const ok3 = existsSync12(agentPath);
|
|
1880
2019
|
checkLine(
|
|
1881
2020
|
i === 0 ? "checking agents" : null,
|
|
1882
2021
|
ok3,
|
|
@@ -1887,8 +2026,8 @@ async function runHealth(cwd2) {
|
|
|
1887
2026
|
}
|
|
1888
2027
|
if (config.tools.mcp.enabled) {
|
|
1889
2028
|
const mcpFile = providerFiles.mcpFile;
|
|
1890
|
-
const mcpPath =
|
|
1891
|
-
const mcpOk =
|
|
2029
|
+
const mcpPath = resolve6(cwd2, mcpFile);
|
|
2030
|
+
const mcpOk = existsSync12(mcpPath);
|
|
1892
2031
|
checkLine("checking MCP", mcpOk, `${mcpFile} valid`);
|
|
1893
2032
|
if (!mcpOk) allOk = false;
|
|
1894
2033
|
}
|
|
@@ -1897,8 +2036,8 @@ async function runHealth(cwd2) {
|
|
|
1897
2036
|
console.error(pc6.red("\u2717 Harness checks failed \u2014 fix the above before running health.sh"));
|
|
1898
2037
|
process.exit(1);
|
|
1899
2038
|
}
|
|
1900
|
-
const scriptPath =
|
|
1901
|
-
if (!
|
|
2039
|
+
const scriptPath = resolve6(cwd2, config.health.scriptPath);
|
|
2040
|
+
if (!existsSync12(scriptPath)) {
|
|
1902
2041
|
console.error(pc6.red(`\u2717 health.sh not found: ${scriptPath}`));
|
|
1903
2042
|
console.error(" Run ahk init first.");
|
|
1904
2043
|
process.exit(1);
|
|
@@ -1928,15 +2067,17 @@ function getProviderHealthFiles(provider) {
|
|
|
1928
2067
|
return { agentsDir: ".opencode/agents", agentExtension: ".md", mcpFile: "opencode.json" };
|
|
1929
2068
|
case "codex-cli":
|
|
1930
2069
|
return { agentsDir: ".codex/agents", agentExtension: ".toml", mcpFile: ".codex/config.toml" };
|
|
2070
|
+
case "grok-cli":
|
|
2071
|
+
return { agentsDir: ".grok/agents", agentExtension: ".md", mcpFile: ".grok/config.toml" };
|
|
1931
2072
|
default:
|
|
1932
2073
|
throw new Error(`Unknown provider: ${provider}`);
|
|
1933
2074
|
}
|
|
1934
2075
|
}
|
|
1935
2076
|
|
|
1936
2077
|
// src/commands/init.ts
|
|
1937
|
-
import { existsSync as
|
|
1938
|
-
import { join as
|
|
1939
|
-
import * as
|
|
2078
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
2079
|
+
import { join as join16 } from "path";
|
|
2080
|
+
import * as p4 from "@clack/prompts";
|
|
1940
2081
|
import pc8 from "picocolors";
|
|
1941
2082
|
|
|
1942
2083
|
// src/schema/init.ts
|
|
@@ -1975,7 +2116,7 @@ var taskDescriptionSchema = v2.pipe(
|
|
|
1975
2116
|
);
|
|
1976
2117
|
|
|
1977
2118
|
// src/utils/form.ts
|
|
1978
|
-
import * as
|
|
2119
|
+
import * as p3 from "@clack/prompts";
|
|
1979
2120
|
import * as v3 from "valibot";
|
|
1980
2121
|
var cliFormWithRetry = async (formFn, schema) => {
|
|
1981
2122
|
while (true) {
|
|
@@ -1983,20 +2124,20 @@ var cliFormWithRetry = async (formFn, schema) => {
|
|
|
1983
2124
|
const result = v3.safeParse(schema, res);
|
|
1984
2125
|
if (result.success) return result.output;
|
|
1985
2126
|
const messages = result.issues.map((i) => i.message).join(", ");
|
|
1986
|
-
|
|
1987
|
-
|
|
2127
|
+
p3.log.error(messages);
|
|
2128
|
+
p3.log.info("Please try again.\n");
|
|
1988
2129
|
}
|
|
1989
2130
|
};
|
|
1990
2131
|
|
|
1991
2132
|
// src/commands/init-helpers.ts
|
|
1992
2133
|
import { randomUUID } from "crypto";
|
|
1993
|
-
import { existsSync as
|
|
1994
|
-
import { join as
|
|
2134
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
2135
|
+
import { join as join15 } from "path";
|
|
1995
2136
|
import pc7 from "picocolors";
|
|
1996
2137
|
function readProjectNameFromPackageJson(cwd2) {
|
|
1997
2138
|
try {
|
|
1998
|
-
const pkgPath2 =
|
|
1999
|
-
if (!
|
|
2139
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2140
|
+
if (!existsSync13(pkgPath2)) return null;
|
|
2000
2141
|
const content = readFileSync8(pkgPath2, "utf8");
|
|
2001
2142
|
const pkg2 = JSON.parse(content);
|
|
2002
2143
|
const name = pkg2?.name;
|
|
@@ -2009,9 +2150,9 @@ function readProjectNameFromPackageJson(cwd2) {
|
|
|
2009
2150
|
function detectConfigExtension(cwd2) {
|
|
2010
2151
|
if (!isLocalInstallSatisfied(cwd2)) return "json";
|
|
2011
2152
|
try {
|
|
2012
|
-
if (
|
|
2013
|
-
const pkgPath2 =
|
|
2014
|
-
if (!
|
|
2153
|
+
if (existsSync13(join15(cwd2, "tsconfig.json"))) return "ts";
|
|
2154
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2155
|
+
if (!existsSync13(pkgPath2)) return "mjs";
|
|
2015
2156
|
const pkg2 = JSON.parse(readFileSync8(pkgPath2, "utf8"));
|
|
2016
2157
|
if (pkg2?.type === "module") return "mjs";
|
|
2017
2158
|
} catch {
|
|
@@ -2092,10 +2233,10 @@ function printWelcomeMessage(projectName) {
|
|
|
2092
2233
|
|
|
2093
2234
|
// src/commands/init.ts
|
|
2094
2235
|
async function reconcileFeatureList(db, installDir, storageDir, firstTask) {
|
|
2095
|
-
const featureListPath =
|
|
2236
|
+
const featureListPath = join16(installDir, storageDir, "feature_list.json");
|
|
2096
2237
|
let existingSeeds = [];
|
|
2097
2238
|
let parseFailed = false;
|
|
2098
|
-
if (
|
|
2239
|
+
if (existsSync14(featureListPath)) {
|
|
2099
2240
|
try {
|
|
2100
2241
|
const parsed = JSON.parse(readFileSync9(featureListPath, "utf8"));
|
|
2101
2242
|
if (!Array.isArray(parsed)) throw new Error("feature_list.json is not a JSON array");
|
|
@@ -2145,86 +2286,60 @@ async function runInit(cwd2, flags) {
|
|
|
2145
2286
|
name = flags.name;
|
|
2146
2287
|
} else {
|
|
2147
2288
|
name = await cliFormWithRetry(async () => {
|
|
2148
|
-
const val = await
|
|
2289
|
+
const val = await p4.text({
|
|
2149
2290
|
message: "Project name",
|
|
2150
2291
|
placeholder: "my-app",
|
|
2151
2292
|
...detectedName && { initialValue: detectedName }
|
|
2152
2293
|
});
|
|
2153
|
-
if (
|
|
2154
|
-
|
|
2294
|
+
if (p4.isCancel(val)) {
|
|
2295
|
+
p4.cancel("Cancelled.");
|
|
2155
2296
|
process.exit(0);
|
|
2156
2297
|
}
|
|
2157
2298
|
return val;
|
|
2158
2299
|
}, initNameSchema);
|
|
2159
2300
|
}
|
|
2160
2301
|
const description = await cliFormWithRetry(async () => {
|
|
2161
|
-
const val = await
|
|
2302
|
+
const val = await p4.text({
|
|
2162
2303
|
message: "Short description (shown to agents as context)",
|
|
2163
2304
|
placeholder: "A REST API for managing notes"
|
|
2164
2305
|
});
|
|
2165
|
-
if (
|
|
2166
|
-
|
|
2306
|
+
if (p4.isCancel(val)) {
|
|
2307
|
+
p4.cancel("Cancelled.");
|
|
2167
2308
|
process.exit(0);
|
|
2168
2309
|
}
|
|
2169
2310
|
return val;
|
|
2170
2311
|
}, initDescriptionSchema);
|
|
2171
2312
|
let provider;
|
|
2172
|
-
if (flags.provider && ["claude-code", "opencode"].includes(flags.provider)) {
|
|
2313
|
+
if (flags.provider && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(flags.provider)) {
|
|
2173
2314
|
provider = flags.provider;
|
|
2174
2315
|
} else {
|
|
2175
|
-
const val = await
|
|
2316
|
+
const val = await p4.select({
|
|
2176
2317
|
message: "AI provider",
|
|
2177
2318
|
options: [
|
|
2178
2319
|
{ value: "opencode", label: "OpenCode" },
|
|
2179
2320
|
{ value: "claude-code", label: "Claude Code" },
|
|
2180
|
-
{ value: "codex-cli", label: "Codex CLI" }
|
|
2321
|
+
{ value: "codex-cli", label: "Codex CLI" },
|
|
2322
|
+
{ value: "grok-cli", label: "Grok CLI" }
|
|
2181
2323
|
]
|
|
2182
2324
|
});
|
|
2183
|
-
if (
|
|
2184
|
-
|
|
2325
|
+
if (p4.isCancel(val)) {
|
|
2326
|
+
p4.cancel("Cancelled.");
|
|
2185
2327
|
process.exit(0);
|
|
2186
2328
|
}
|
|
2187
2329
|
provider = val;
|
|
2188
2330
|
}
|
|
2189
|
-
const
|
|
2190
|
-
{ key: "lead", label: "Lead" },
|
|
2191
|
-
{ key: "explorer", label: "Explorer" },
|
|
2192
|
-
{ key: "consultant", label: "Consultant" },
|
|
2193
|
-
{ key: "builder", label: "Builder" },
|
|
2194
|
-
{ key: "reviewer", label: "Reviewer" }
|
|
2195
|
-
];
|
|
2196
|
-
const claudeAgentModels = {};
|
|
2197
|
-
if (provider === "claude-code") {
|
|
2198
|
-
for (const agent of AGENT_LABELS) {
|
|
2199
|
-
const val = await p3.select({
|
|
2200
|
-
message: `Model for ${agent.label}`,
|
|
2201
|
-
options: [
|
|
2202
|
-
{ value: "inherit", label: "inherit (default)" },
|
|
2203
|
-
{ value: "haiku", label: "haiku" },
|
|
2204
|
-
{ value: "sonnet", label: "sonnet" },
|
|
2205
|
-
{ value: "opus", label: "opus" },
|
|
2206
|
-
{ value: "fable", label: "fable" }
|
|
2207
|
-
],
|
|
2208
|
-
initialValue: "inherit"
|
|
2209
|
-
});
|
|
2210
|
-
if (p3.isCancel(val)) {
|
|
2211
|
-
p3.cancel("Cancelled.");
|
|
2212
|
-
process.exit(0);
|
|
2213
|
-
}
|
|
2214
|
-
claudeAgentModels[agent.key] = val;
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2331
|
+
const claudeAgentModels = await promptClaudeAgentModels(provider);
|
|
2217
2332
|
let docsPath;
|
|
2218
2333
|
if (flags.docs) {
|
|
2219
2334
|
docsPath = flags.docs;
|
|
2220
2335
|
} else {
|
|
2221
2336
|
docsPath = await cliFormWithRetry(async () => {
|
|
2222
|
-
const val = await
|
|
2337
|
+
const val = await p4.text({
|
|
2223
2338
|
message: "Docs folder path (agents will search here)",
|
|
2224
2339
|
initialValue: "./docs"
|
|
2225
2340
|
});
|
|
2226
|
-
if (
|
|
2227
|
-
|
|
2341
|
+
if (p4.isCancel(val)) {
|
|
2342
|
+
p4.cancel("Cancelled.");
|
|
2228
2343
|
process.exit(0);
|
|
2229
2344
|
}
|
|
2230
2345
|
return val;
|
|
@@ -2234,7 +2349,7 @@ async function runInit(cwd2, flags) {
|
|
|
2234
2349
|
if (flags.storageScope && ["local", "global"].includes(flags.storageScope)) {
|
|
2235
2350
|
storageScope = flags.storageScope;
|
|
2236
2351
|
} else {
|
|
2237
|
-
const val = await
|
|
2352
|
+
const val = await p4.select({
|
|
2238
2353
|
message: "Storage scope",
|
|
2239
2354
|
options: [
|
|
2240
2355
|
{ value: "local", label: "Local \u2014 .harness/harness.db lives in this project" },
|
|
@@ -2245,8 +2360,8 @@ async function runInit(cwd2, flags) {
|
|
|
2245
2360
|
],
|
|
2246
2361
|
initialValue: "local"
|
|
2247
2362
|
});
|
|
2248
|
-
if (
|
|
2249
|
-
|
|
2363
|
+
if (p4.isCancel(val)) {
|
|
2364
|
+
p4.cancel("Cancelled.");
|
|
2250
2365
|
process.exit(0);
|
|
2251
2366
|
}
|
|
2252
2367
|
storageScope = val;
|
|
@@ -2255,7 +2370,7 @@ async function runInit(cwd2, flags) {
|
|
|
2255
2370
|
if (flags.tasks && ["local", "jira", "linear"].includes(flags.tasks)) {
|
|
2256
2371
|
tasksAdapter = flags.tasks;
|
|
2257
2372
|
} else {
|
|
2258
|
-
const val = await
|
|
2373
|
+
const val = await p4.select({
|
|
2259
2374
|
message: "Task adapter",
|
|
2260
2375
|
options: [
|
|
2261
2376
|
{ value: "local", label: "Local (feature_list.json)" },
|
|
@@ -2263,50 +2378,50 @@ async function runInit(cwd2, flags) {
|
|
|
2263
2378
|
{ value: "linear", label: "Linear (coming soon)" }
|
|
2264
2379
|
]
|
|
2265
2380
|
});
|
|
2266
|
-
if (
|
|
2267
|
-
|
|
2381
|
+
if (p4.isCancel(val)) {
|
|
2382
|
+
p4.cancel("Cancelled");
|
|
2268
2383
|
process.exit(0);
|
|
2269
2384
|
}
|
|
2270
2385
|
tasksAdapter = val;
|
|
2271
2386
|
}
|
|
2272
|
-
const addFirstTask = await
|
|
2273
|
-
if (
|
|
2274
|
-
|
|
2387
|
+
const addFirstTask = await p4.confirm({ message: "Add your first task now?", initialValue: false });
|
|
2388
|
+
if (p4.isCancel(addFirstTask)) {
|
|
2389
|
+
p4.cancel("Cancelled");
|
|
2275
2390
|
process.exit(0);
|
|
2276
2391
|
}
|
|
2277
2392
|
let firstTask;
|
|
2278
2393
|
if (addFirstTask) {
|
|
2279
2394
|
const taskTitle = await cliFormWithRetry(async () => {
|
|
2280
|
-
const val = await
|
|
2281
|
-
if (
|
|
2282
|
-
|
|
2395
|
+
const val = await p4.text({ message: "Task title" });
|
|
2396
|
+
if (p4.isCancel(val)) {
|
|
2397
|
+
p4.cancel("Cancelled");
|
|
2283
2398
|
process.exit(0);
|
|
2284
2399
|
}
|
|
2285
2400
|
return val.trim();
|
|
2286
2401
|
}, taskTitleSchema);
|
|
2287
2402
|
const taskDesc = await cliFormWithRetry(async () => {
|
|
2288
|
-
const val = await
|
|
2289
|
-
if (
|
|
2290
|
-
|
|
2403
|
+
const val = await p4.text({ message: "Task description", placeholder: "What and why" });
|
|
2404
|
+
if (p4.isCancel(val)) {
|
|
2405
|
+
p4.cancel("Cancelled");
|
|
2291
2406
|
process.exit(0);
|
|
2292
2407
|
}
|
|
2293
2408
|
return val.trim();
|
|
2294
2409
|
}, taskDescriptionSchema);
|
|
2295
2410
|
const acceptance = [];
|
|
2296
|
-
|
|
2411
|
+
p4.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
2297
2412
|
while (true) {
|
|
2298
|
-
const criterionVal = await
|
|
2413
|
+
const criterionVal = await p4.text({
|
|
2299
2414
|
message: ">",
|
|
2300
2415
|
placeholder: "Criterion (or press Enter to finish)"
|
|
2301
2416
|
});
|
|
2302
|
-
if (
|
|
2417
|
+
if (p4.isCancel(criterionVal) || !criterionVal || !criterionVal.trim()) break;
|
|
2303
2418
|
acceptance.push(criterionVal.trim());
|
|
2304
2419
|
}
|
|
2305
2420
|
firstTask = { title: taskTitle, description: taskDesc, acceptance };
|
|
2306
2421
|
}
|
|
2307
2422
|
let configExt = "ts";
|
|
2308
2423
|
let featureListParseFailedPath = null;
|
|
2309
|
-
const spinner6 =
|
|
2424
|
+
const spinner6 = p4.spinner();
|
|
2310
2425
|
spinner6.start("Scaffolding...");
|
|
2311
2426
|
try {
|
|
2312
2427
|
const config = applyConfigDefaults({
|
|
@@ -2332,20 +2447,20 @@ async function runInit(cwd2, flags) {
|
|
|
2332
2447
|
scope: config.storage.scope,
|
|
2333
2448
|
projectId: config.storage.projectId
|
|
2334
2449
|
});
|
|
2335
|
-
|
|
2336
|
-
|
|
2450
|
+
writeFileSync8(join16(installDir, configFileName), configContent, "utf8");
|
|
2451
|
+
mkdirSync7(join16(installDir, config.storage.dir), { recursive: true });
|
|
2337
2452
|
const db = await openDB(config, installDir);
|
|
2338
2453
|
await db.writeStorageState(installDir);
|
|
2339
2454
|
await materializer.scaffold(config, { cwd: installDir, firstTask, claudeAgentModels });
|
|
2340
2455
|
const { parseFailed } = await reconcileFeatureList(db, installDir, config.storage.dir, firstTask);
|
|
2341
2456
|
if (parseFailed) {
|
|
2342
|
-
featureListParseFailedPath =
|
|
2457
|
+
featureListParseFailedPath = join16(config.storage.dir, "feature_list.json");
|
|
2343
2458
|
}
|
|
2344
2459
|
await db.close();
|
|
2345
2460
|
spinner6.stop("");
|
|
2346
2461
|
} catch (err) {
|
|
2347
2462
|
spinner6.stop("Failed");
|
|
2348
|
-
|
|
2463
|
+
p4.log.error(err instanceof Error ? err.message : String(err));
|
|
2349
2464
|
throw err;
|
|
2350
2465
|
}
|
|
2351
2466
|
if (featureListParseFailedPath) {
|
|
@@ -2354,8 +2469,13 @@ async function runInit(cwd2, flags) {
|
|
|
2354
2469
|
);
|
|
2355
2470
|
}
|
|
2356
2471
|
console.log(pc8.green("\u2713 Scaffolded harness in current directory"));
|
|
2357
|
-
const
|
|
2358
|
-
|
|
2472
|
+
const PROVIDER_SUMMARY_INFO = {
|
|
2473
|
+
"claude-code": { agentsDir: ".claude/agents/", mcpFile: ".mcp.json" },
|
|
2474
|
+
opencode: { agentsDir: ".opencode/agents/", mcpFile: "./opencode.json" },
|
|
2475
|
+
"codex-cli": { agentsDir: ".codex/agents/", mcpFile: ".codex/config.toml" },
|
|
2476
|
+
"grok-cli": { agentsDir: ".grok/agents/", mcpFile: ".grok/config.toml" }
|
|
2477
|
+
};
|
|
2478
|
+
const { agentsDir, mcpFile } = PROVIDER_SUMMARY_INFO[provider];
|
|
2359
2479
|
console.log("");
|
|
2360
2480
|
console.log(pc8.green(`\u2713 agent-harness-kit.config.${configExt}`));
|
|
2361
2481
|
console.log(pc8.green("\u2713 AGENTS.md"));
|
|
@@ -2392,24 +2512,25 @@ async function runInit(cwd2, flags) {
|
|
|
2392
2512
|
}
|
|
2393
2513
|
|
|
2394
2514
|
// src/commands/migrate.ts
|
|
2395
|
-
import * as
|
|
2515
|
+
import * as p5 from "@clack/prompts";
|
|
2396
2516
|
import pc9 from "picocolors";
|
|
2397
2517
|
async function runMigrate(cwd2, opts) {
|
|
2398
2518
|
const config = await loadConfig(cwd2);
|
|
2399
2519
|
let target;
|
|
2400
|
-
if (opts.to && ["claude-code", "opencode", "codex-cli"].includes(opts.to)) {
|
|
2520
|
+
if (opts.to && ["claude-code", "opencode", "codex-cli", "grok-cli"].includes(opts.to)) {
|
|
2401
2521
|
target = opts.to;
|
|
2402
2522
|
} else {
|
|
2403
|
-
const val = await
|
|
2523
|
+
const val = await p5.select({
|
|
2404
2524
|
message: "Migrate to provider",
|
|
2405
2525
|
options: [
|
|
2406
2526
|
{ value: "claude-code", label: "Claude Code" },
|
|
2407
2527
|
{ value: "opencode", label: "OpenCode" },
|
|
2408
|
-
{ value: "codex-cli", label: "Codex CLI" }
|
|
2528
|
+
{ value: "codex-cli", label: "Codex CLI" },
|
|
2529
|
+
{ value: "grok-cli", label: "Grok CLI" }
|
|
2409
2530
|
]
|
|
2410
2531
|
});
|
|
2411
|
-
if (
|
|
2412
|
-
|
|
2532
|
+
if (p5.isCancel(val)) {
|
|
2533
|
+
p5.cancel("Cancelled.");
|
|
2413
2534
|
process.exit(0);
|
|
2414
2535
|
}
|
|
2415
2536
|
target = val;
|
|
@@ -2418,25 +2539,25 @@ async function runMigrate(cwd2, opts) {
|
|
|
2418
2539
|
console.log(pc9.dim(`Already on ${target} \u2014 nothing to migrate.`));
|
|
2419
2540
|
return;
|
|
2420
2541
|
}
|
|
2421
|
-
const spinner6 =
|
|
2542
|
+
const spinner6 = p5.spinner();
|
|
2422
2543
|
spinner6.start(`Migrating from ${config.provider} to ${target}...`);
|
|
2423
2544
|
try {
|
|
2424
2545
|
const targetMaterializer = getMaterializer(target);
|
|
2425
2546
|
await targetMaterializer.build(config, cwd2);
|
|
2426
2547
|
spinner6.stop(pc9.green(`Migrated to ${target}`));
|
|
2427
|
-
|
|
2428
|
-
|
|
2548
|
+
p5.log.warn(`Update agent-harness-kit.config.ts: set provider: '${target}'`);
|
|
2549
|
+
p5.log.warn(`Then run: ahk build`);
|
|
2429
2550
|
} catch (err) {
|
|
2430
2551
|
spinner6.stop(pc9.red("Migration failed"));
|
|
2431
|
-
|
|
2552
|
+
p5.log.error(err instanceof Error ? err.message : String(err));
|
|
2432
2553
|
process.exit(1);
|
|
2433
2554
|
}
|
|
2434
2555
|
}
|
|
2435
2556
|
|
|
2436
2557
|
// src/commands/migrate-storage.ts
|
|
2437
|
-
import { copyFileSync, existsSync as
|
|
2558
|
+
import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync8, rmSync, writeFileSync as writeFileSync9 } from "fs";
|
|
2438
2559
|
import { homedir as homedir3 } from "os";
|
|
2439
|
-
import { dirname as dirname7, join as
|
|
2560
|
+
import { dirname as dirname7, join as join17, resolve as resolve7 } from "path";
|
|
2440
2561
|
import pc10 from "picocolors";
|
|
2441
2562
|
function log5(msg) {
|
|
2442
2563
|
console.log(msg);
|
|
@@ -2448,17 +2569,17 @@ function defaultMarkdownPathForConfig(config) {
|
|
|
2448
2569
|
return config.storage.scope === "local" ? config.storage.markdownFallback.path : DEFAULT_MARKDOWN_PATH;
|
|
2449
2570
|
}
|
|
2450
2571
|
function currentMdPathForScope(scope, config, cwd2, homeDir) {
|
|
2451
|
-
return scope === "global" ?
|
|
2572
|
+
return scope === "global" ? join17(resolveGlobalStorageDir(config, homeDir), "current.md") : resolve7(cwd2, defaultMarkdownPathForConfig(config));
|
|
2452
2573
|
}
|
|
2453
2574
|
function defaultSqlitePathForConfig(config) {
|
|
2454
2575
|
return config.storage.scope === "local" && config.database.type === "sqlite" ? config.storage.sqlitePath ?? DEFAULT_SQLITE_PATH : DEFAULT_SQLITE_PATH;
|
|
2455
2576
|
}
|
|
2456
2577
|
async function backupDestination(cwd2, storageDir, data) {
|
|
2457
|
-
const backupsDir =
|
|
2458
|
-
const path =
|
|
2578
|
+
const backupsDir = resolve7(cwd2, storageDir, "backups");
|
|
2579
|
+
const path = join17(backupsDir, `pre-migrate-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
2459
2580
|
try {
|
|
2460
|
-
|
|
2461
|
-
|
|
2581
|
+
mkdirSync8(backupsDir, { recursive: true });
|
|
2582
|
+
writeFileSync9(path, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
2462
2583
|
} catch (err) {
|
|
2463
2584
|
throw new Error(
|
|
2464
2585
|
`Could not write destination backup to ${path} (${err instanceof Error ? err.message : String(err)}). Aborting migration WITHOUT touching the destination \u2014 nothing was overwritten.`
|
|
@@ -2467,14 +2588,14 @@ async function backupDestination(cwd2, storageDir, data) {
|
|
|
2467
2588
|
return path;
|
|
2468
2589
|
}
|
|
2469
2590
|
function copySqliteFile(srcPath, destPath) {
|
|
2470
|
-
|
|
2591
|
+
mkdirSync8(dirname7(destPath), { recursive: true });
|
|
2471
2592
|
copyFileSync(srcPath, destPath);
|
|
2472
2593
|
for (const suffix of ["-wal", "-shm"]) {
|
|
2473
|
-
if (
|
|
2594
|
+
if (existsSync15(`${srcPath}${suffix}`)) {
|
|
2474
2595
|
copyFileSync(`${srcPath}${suffix}`, `${destPath}${suffix}`);
|
|
2475
2596
|
}
|
|
2476
2597
|
}
|
|
2477
|
-
if (!
|
|
2598
|
+
if (!existsSync15(destPath)) {
|
|
2478
2599
|
throw new Error(`Copy verification failed: ${destPath} does not exist after copy.`);
|
|
2479
2600
|
}
|
|
2480
2601
|
}
|
|
@@ -2533,7 +2654,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2533
2654
|
return migrateAcrossDbType(cwd2, config, homeDir, realScope, opts);
|
|
2534
2655
|
}
|
|
2535
2656
|
async function probeTaskCount(dbPath) {
|
|
2536
|
-
if (!
|
|
2657
|
+
if (!existsSync15(dbPath)) return 0;
|
|
2537
2658
|
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2538
2659
|
const driver = new SQLiteDriver(dbPath);
|
|
2539
2660
|
try {
|
|
@@ -2550,10 +2671,10 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2550
2671
|
const destDb = resolveSqlitePathForScope(toScope, sqlitePath, cwd2, config, homeDir);
|
|
2551
2672
|
const srcMd = currentMdPathForScope(fromScope, config, cwd2, homeDir);
|
|
2552
2673
|
const destMd = currentMdPathForScope(toScope, config, cwd2, homeDir);
|
|
2553
|
-
if (!
|
|
2674
|
+
if (!existsSync15(srcDb)) {
|
|
2554
2675
|
fail(`Source database not found at ${srcDb} (expected ${fromScope} scope) \u2014 nothing to move.`);
|
|
2555
2676
|
}
|
|
2556
|
-
const destExists =
|
|
2677
|
+
const destExists = existsSync15(destDb);
|
|
2557
2678
|
if (destExists) {
|
|
2558
2679
|
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
2559
2680
|
const destDriver = new SQLiteDriver(destDb);
|
|
@@ -2591,15 +2712,15 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2591
2712
|
}
|
|
2592
2713
|
copySqliteFile(srcDb, destDb);
|
|
2593
2714
|
log5(pc10.green(`\u2713 Copied database ${srcDb} \u2192 ${destDb}`));
|
|
2594
|
-
if (
|
|
2595
|
-
|
|
2715
|
+
if (existsSync15(srcMd)) {
|
|
2716
|
+
mkdirSync8(dirname7(destMd), { recursive: true });
|
|
2596
2717
|
copyFileSync(srcMd, destMd);
|
|
2597
2718
|
log5(pc10.green(`\u2713 Copied current.md ${srcMd} \u2192 ${destMd}`));
|
|
2598
2719
|
}
|
|
2599
2720
|
rmSync(srcDb, { force: true });
|
|
2600
2721
|
rmSync(`${srcDb}-wal`, { force: true });
|
|
2601
2722
|
rmSync(`${srcDb}-shm`, { force: true });
|
|
2602
|
-
if (
|
|
2723
|
+
if (existsSync15(srcMd) && srcMd !== destMd) rmSync(srcMd, { force: true });
|
|
2603
2724
|
const db = await openDB(config, cwd2, homeDir);
|
|
2604
2725
|
try {
|
|
2605
2726
|
await db.writeStorageState(cwd2);
|
|
@@ -2611,7 +2732,7 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2611
2732
|
async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
2612
2733
|
const sqlitePath = defaultSqlitePathForConfig(config);
|
|
2613
2734
|
const srcPath = resolveSqlitePathForScope(sourceScope, sqlitePath, cwd2, config, homeDir);
|
|
2614
|
-
if (!
|
|
2735
|
+
if (!existsSync15(srcPath)) {
|
|
2615
2736
|
fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
|
|
2616
2737
|
}
|
|
2617
2738
|
const { SQLiteDriver } = await import("./sqlite-5OWKTUUZ.js");
|
|
@@ -2673,55 +2794,121 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2673
2794
|
}
|
|
2674
2795
|
}
|
|
2675
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
|
+
|
|
2676
2850
|
// src/commands/reset.ts
|
|
2677
|
-
import { existsSync as
|
|
2851
|
+
import { existsSync as existsSync16, readdirSync, rmSync as rmSync2 } from "fs";
|
|
2678
2852
|
import { homedir as homedir4 } from "os";
|
|
2679
|
-
import { join as
|
|
2680
|
-
import * as
|
|
2681
|
-
import
|
|
2853
|
+
import { join as join19, resolve as resolve8 } from "path";
|
|
2854
|
+
import * as p6 from "@clack/prompts";
|
|
2855
|
+
import pc12 from "picocolors";
|
|
2682
2856
|
var AGENT_MD_FILES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2857
|
+
var PROVIDER_AGENT_DIRS = {
|
|
2858
|
+
"claude-code": ".claude/agents",
|
|
2859
|
+
opencode: ".opencode/agents",
|
|
2860
|
+
"codex-cli": ".codex/agents",
|
|
2861
|
+
"grok-cli": ".grok/agents"
|
|
2862
|
+
};
|
|
2863
|
+
var PROVIDER_AGENT_EXT = {
|
|
2864
|
+
"claude-code": ".md",
|
|
2865
|
+
opencode: ".md",
|
|
2866
|
+
"codex-cli": ".toml",
|
|
2867
|
+
"grok-cli": ".md"
|
|
2868
|
+
};
|
|
2683
2869
|
async function resetAgentMds(cwd2, provider) {
|
|
2684
|
-
const agentDir = provider
|
|
2685
|
-
const agentDirPath =
|
|
2686
|
-
|
|
2687
|
-
|
|
2870
|
+
const agentDir = PROVIDER_AGENT_DIRS[provider];
|
|
2871
|
+
const agentDirPath = resolve8(cwd2, agentDir);
|
|
2872
|
+
const agentExt = PROVIDER_AGENT_EXT[provider];
|
|
2873
|
+
if (!existsSync16(agentDirPath)) {
|
|
2874
|
+
console.log(pc12.yellow(` Skipping agent files \u2014 directory not found: ${agentDirPath}`));
|
|
2688
2875
|
return;
|
|
2689
2876
|
}
|
|
2690
2877
|
const existingFiles = [];
|
|
2691
2878
|
try {
|
|
2692
2879
|
const files = readdirSync(agentDirPath);
|
|
2693
2880
|
for (const f of files) {
|
|
2694
|
-
if (f.endsWith(
|
|
2881
|
+
if (f.endsWith(agentExt) && AGENT_MD_FILES.includes(f.replace(agentExt, ""))) {
|
|
2695
2882
|
existingFiles.push(f);
|
|
2696
2883
|
}
|
|
2697
2884
|
}
|
|
2698
2885
|
} catch {
|
|
2699
|
-
console.log(
|
|
2886
|
+
console.log(pc12.yellow(` Skipping agent files \u2014 ${agentDirPath} is not readable`));
|
|
2700
2887
|
return;
|
|
2701
2888
|
}
|
|
2702
2889
|
if (existingFiles.length === 0) {
|
|
2703
|
-
console.log(
|
|
2890
|
+
console.log(pc12.yellow(` No agent MD files found in ${agentDir}/`));
|
|
2704
2891
|
return;
|
|
2705
2892
|
}
|
|
2706
2893
|
for (const file of existingFiles) {
|
|
2707
|
-
const confirm3 = await
|
|
2894
|
+
const confirm3 = await p6.confirm({
|
|
2708
2895
|
message: `Remove ${file}?`,
|
|
2709
2896
|
initialValue: true
|
|
2710
2897
|
});
|
|
2711
|
-
if (
|
|
2712
|
-
console.log(
|
|
2898
|
+
if (p6.isCancel(confirm3)) {
|
|
2899
|
+
console.log(pc12.red(" Cancelled by user."));
|
|
2713
2900
|
return;
|
|
2714
2901
|
}
|
|
2715
2902
|
if (confirm3) {
|
|
2716
2903
|
try {
|
|
2717
|
-
const filePath =
|
|
2904
|
+
const filePath = join19(agentDirPath, file);
|
|
2718
2905
|
rmSync2(filePath, { force: true });
|
|
2719
|
-
console.log(
|
|
2906
|
+
console.log(pc12.green(` Removed ${file}`));
|
|
2720
2907
|
} catch {
|
|
2721
|
-
console.error(
|
|
2908
|
+
console.error(pc12.red(` Failed to remove ${file}`));
|
|
2722
2909
|
}
|
|
2723
2910
|
} else {
|
|
2724
|
-
console.log(
|
|
2911
|
+
console.log(pc12.cyan(` Skipped ${file}`));
|
|
2725
2912
|
}
|
|
2726
2913
|
}
|
|
2727
2914
|
}
|
|
@@ -2730,47 +2917,47 @@ async function runReset(cwd2, opts) {
|
|
|
2730
2917
|
try {
|
|
2731
2918
|
config = await loadConfig(cwd2);
|
|
2732
2919
|
} catch {
|
|
2733
|
-
console.error(
|
|
2920
|
+
console.error(pc12.red("\u2717 No agent-harness-kit.config found. Run: ahk init"));
|
|
2734
2921
|
process.exit(1);
|
|
2735
2922
|
}
|
|
2736
2923
|
const storageDir = config.storage.dir || ".harness";
|
|
2737
2924
|
const dbPath = config.database.type === "sqlite" ? resolveSqlitePath(config, cwd2, homedir4()) : null;
|
|
2738
|
-
const featureListPath =
|
|
2925
|
+
const featureListPath = resolve8(cwd2, storageDir, "feature_list.json");
|
|
2739
2926
|
let resetDb = false;
|
|
2740
2927
|
let resetFeatureList = false;
|
|
2741
2928
|
let resetAgentMdsFlag = false;
|
|
2742
|
-
if (dbPath &&
|
|
2929
|
+
if (dbPath && existsSync16(dbPath)) {
|
|
2743
2930
|
if (opts.force) {
|
|
2744
2931
|
resetDb = true;
|
|
2745
2932
|
} else {
|
|
2746
2933
|
if (config.database.type !== "sqlite") {
|
|
2747
|
-
console.log(
|
|
2934
|
+
console.log(pc12.yellow(` Skipping DB reset \u2014 database type "${config.database.type}" is not managed by this command.`));
|
|
2748
2935
|
resetDb = false;
|
|
2749
2936
|
} else {
|
|
2750
|
-
const confirm3 = await
|
|
2937
|
+
const confirm3 = await p6.confirm({
|
|
2751
2938
|
message: `Delete database (${dbPath})?`,
|
|
2752
2939
|
initialValue: true
|
|
2753
2940
|
});
|
|
2754
|
-
if (
|
|
2755
|
-
console.log(
|
|
2941
|
+
if (p6.isCancel(confirm3)) {
|
|
2942
|
+
console.log(pc12.red(" Cancelled by user."));
|
|
2756
2943
|
return;
|
|
2757
2944
|
}
|
|
2758
2945
|
resetDb = confirm3;
|
|
2759
2946
|
}
|
|
2760
2947
|
}
|
|
2761
2948
|
} else if (!dbPath) {
|
|
2762
|
-
console.log(
|
|
2949
|
+
console.log(pc12.dim(` Skipping DB reset \u2014 remote ${config.database.type} database is not managed by this command.`));
|
|
2763
2950
|
}
|
|
2764
|
-
if (
|
|
2951
|
+
if (existsSync16(featureListPath)) {
|
|
2765
2952
|
if (opts.force) {
|
|
2766
2953
|
resetFeatureList = true;
|
|
2767
2954
|
} else {
|
|
2768
|
-
const confirm3 = await
|
|
2955
|
+
const confirm3 = await p6.confirm({
|
|
2769
2956
|
message: `Delete feature list (${storageDir}/feature_list.json)?`,
|
|
2770
2957
|
initialValue: true
|
|
2771
2958
|
});
|
|
2772
|
-
if (
|
|
2773
|
-
console.log(
|
|
2959
|
+
if (p6.isCancel(confirm3)) {
|
|
2960
|
+
console.log(pc12.red(" Cancelled by user."));
|
|
2774
2961
|
return;
|
|
2775
2962
|
}
|
|
2776
2963
|
resetFeatureList = confirm3;
|
|
@@ -2784,17 +2971,17 @@ async function runReset(cwd2, opts) {
|
|
|
2784
2971
|
rmSync2(dbPath, { force: true });
|
|
2785
2972
|
rmSync2(`${dbPath}-wal`, { force: true });
|
|
2786
2973
|
rmSync2(`${dbPath}-shm`, { force: true });
|
|
2787
|
-
console.log(
|
|
2974
|
+
console.log(pc12.green(` \u2713 Removed ${dbPath}`));
|
|
2788
2975
|
} catch {
|
|
2789
|
-
console.error(
|
|
2976
|
+
console.error(pc12.red(` \u2717 Failed to remove ${dbPath}`));
|
|
2790
2977
|
}
|
|
2791
2978
|
}
|
|
2792
2979
|
if (resetFeatureList) {
|
|
2793
2980
|
try {
|
|
2794
2981
|
rmSync2(featureListPath, { force: true });
|
|
2795
|
-
console.log(
|
|
2982
|
+
console.log(pc12.green(` \u2713 Removed ${storageDir}/feature_list.json`));
|
|
2796
2983
|
} catch {
|
|
2797
|
-
console.error(
|
|
2984
|
+
console.error(pc12.red(` \u2717 Failed to remove ${featureListPath}`));
|
|
2798
2985
|
}
|
|
2799
2986
|
}
|
|
2800
2987
|
if (resetAgentMdsFlag) {
|
|
@@ -2802,16 +2989,16 @@ async function runReset(cwd2, opts) {
|
|
|
2802
2989
|
await resetAgentMds(cwd2, opts.provider || "claude-code");
|
|
2803
2990
|
}
|
|
2804
2991
|
if (!resetDb && !resetFeatureList && !resetAgentMdsFlag) {
|
|
2805
|
-
console.log(
|
|
2992
|
+
console.log(pc12.yellow(" Nothing to reset (all items missing or skipped)."));
|
|
2806
2993
|
return;
|
|
2807
2994
|
}
|
|
2808
2995
|
console.log("");
|
|
2809
|
-
console.log(
|
|
2996
|
+
console.log(pc12.green('\u2713 Reset complete. Run "ahk init" to scaffold a fresh harness.'));
|
|
2810
2997
|
}
|
|
2811
2998
|
|
|
2812
2999
|
// src/core/mcp-server.ts
|
|
2813
|
-
import { existsSync as
|
|
2814
|
-
import { join as
|
|
3000
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync10 } from "fs";
|
|
3001
|
+
import { join as join21, resolve as resolve9 } from "path";
|
|
2815
3002
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2816
3003
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2817
3004
|
import {
|
|
@@ -2820,8 +3007,8 @@ import {
|
|
|
2820
3007
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2821
3008
|
|
|
2822
3009
|
// src/core/permissions-check.ts
|
|
2823
|
-
import { existsSync as
|
|
2824
|
-
import { join as
|
|
3010
|
+
import { existsSync as existsSync17 } from "fs";
|
|
3011
|
+
import { join as join20 } from "path";
|
|
2825
3012
|
var AGENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
2826
3013
|
function checkPermissionsSync(cwd2, config) {
|
|
2827
3014
|
if (config.provider !== "claude-code") {
|
|
@@ -2830,8 +3017,8 @@ function checkPermissionsSync(cwd2, config) {
|
|
|
2830
3017
|
const agents = {};
|
|
2831
3018
|
let in_sync = true;
|
|
2832
3019
|
for (const agent of AGENTS) {
|
|
2833
|
-
const filePath =
|
|
2834
|
-
const exists =
|
|
3020
|
+
const filePath = join20(cwd2, ".claude", "agents", `${agent}.md`);
|
|
3021
|
+
const exists = existsSync17(filePath);
|
|
2835
3022
|
if (!exists) in_sync = false;
|
|
2836
3023
|
agents[agent] = exists ? { ok: true } : { ok: false, reason: "missing_file" };
|
|
2837
3024
|
}
|
|
@@ -3129,7 +3316,7 @@ var TOOLS = [
|
|
|
3129
3316
|
];
|
|
3130
3317
|
async function startMcpServer(config, cwd2) {
|
|
3131
3318
|
const db = await openDB(config, cwd2);
|
|
3132
|
-
const docsPath =
|
|
3319
|
+
const docsPath = resolve9(cwd2, config.project.docsPath);
|
|
3133
3320
|
const server = new Server(
|
|
3134
3321
|
{ name: "agent-harness-kit", version: VERSION },
|
|
3135
3322
|
{ capabilities: { tools: {} } }
|
|
@@ -3292,8 +3479,8 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3292
3479
|
return ok2(JSON.stringify(result));
|
|
3293
3480
|
}
|
|
3294
3481
|
case "deps.snapshot": {
|
|
3295
|
-
const pkgPath2 =
|
|
3296
|
-
if (!
|
|
3482
|
+
const pkgPath2 = join21(cwd2, "package.json");
|
|
3483
|
+
if (!existsSync18(pkgPath2)) {
|
|
3297
3484
|
return ok2("package.json not found in project root", true);
|
|
3298
3485
|
}
|
|
3299
3486
|
const pkg2 = JSON.parse(readFileSync10(pkgPath2, "utf8"));
|
|
@@ -3302,9 +3489,9 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3302
3489
|
dependencies: pkg2.dependencies ?? {},
|
|
3303
3490
|
devDependencies: pkg2.devDependencies ?? {}
|
|
3304
3491
|
};
|
|
3305
|
-
const harnessDir =
|
|
3306
|
-
|
|
3307
|
-
|
|
3492
|
+
const harnessDir = join21(cwd2, ".harness");
|
|
3493
|
+
mkdirSync9(harnessDir, { recursive: true });
|
|
3494
|
+
writeFileSync10(join21(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
3308
3495
|
return ok2(
|
|
3309
3496
|
JSON.stringify({
|
|
3310
3497
|
message: "Snapshot saved to .harness/deps-lock.json",
|
|
@@ -3313,12 +3500,12 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3313
3500
|
);
|
|
3314
3501
|
}
|
|
3315
3502
|
case "deps.check": {
|
|
3316
|
-
const pkgPath2 =
|
|
3317
|
-
const lockPath =
|
|
3318
|
-
if (!
|
|
3503
|
+
const pkgPath2 = join21(cwd2, "package.json");
|
|
3504
|
+
const lockPath = join21(cwd2, ".harness", "deps-lock.json");
|
|
3505
|
+
if (!existsSync18(pkgPath2)) {
|
|
3319
3506
|
return ok2("package.json not found in project root", true);
|
|
3320
3507
|
}
|
|
3321
|
-
if (!
|
|
3508
|
+
if (!existsSync18(lockPath)) {
|
|
3322
3509
|
return ok2(
|
|
3323
3510
|
JSON.stringify({
|
|
3324
3511
|
status: "no-snapshot",
|
|
@@ -3420,7 +3607,7 @@ function collectMarkdownFiles(dir) {
|
|
|
3420
3607
|
const files = [];
|
|
3421
3608
|
try {
|
|
3422
3609
|
for (const entry of readdirSync2(dir)) {
|
|
3423
|
-
const full =
|
|
3610
|
+
const full = join21(dir, entry);
|
|
3424
3611
|
const stat = statSync(full);
|
|
3425
3612
|
if (stat.isDirectory()) {
|
|
3426
3613
|
files.push(...collectMarkdownFiles(full));
|
|
@@ -3477,12 +3664,12 @@ async function runServe(cwd2, opts) {
|
|
|
3477
3664
|
|
|
3478
3665
|
// src/commands/status.ts
|
|
3479
3666
|
import Table from "cli-table3";
|
|
3480
|
-
import
|
|
3667
|
+
import pc13 from "picocolors";
|
|
3481
3668
|
var STATUS_COLOR = {
|
|
3482
|
-
pending: (s) =>
|
|
3483
|
-
in_progress: (s) =>
|
|
3484
|
-
done: (s) =>
|
|
3485
|
-
blocked: (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)
|
|
3486
3673
|
};
|
|
3487
3674
|
async function runStatus(cwd2, opts) {
|
|
3488
3675
|
const config = await loadConfig(cwd2);
|
|
@@ -3503,11 +3690,11 @@ async function runStatus(cwd2, opts) {
|
|
|
3503
3690
|
return;
|
|
3504
3691
|
}
|
|
3505
3692
|
if (tasks.length === 0) {
|
|
3506
|
-
console.log(
|
|
3693
|
+
console.log(pc13.dim("No tasks yet. Run: ahk task add"));
|
|
3507
3694
|
return;
|
|
3508
3695
|
}
|
|
3509
3696
|
const table = new Table({
|
|
3510
|
-
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) =>
|
|
3697
|
+
head: ["ID", "Slug", "Title", "Status", "Assigned", "Started"].map((h) => pc13.bold(h)),
|
|
3511
3698
|
style: { head: [], border: [] }
|
|
3512
3699
|
});
|
|
3513
3700
|
for (const t of tasks) {
|
|
@@ -3525,12 +3712,12 @@ async function runStatus(cwd2, opts) {
|
|
|
3525
3712
|
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
3526
3713
|
if (inProgress.length > 0) {
|
|
3527
3714
|
console.log("");
|
|
3528
|
-
console.log(
|
|
3715
|
+
console.log(pc13.bold("Active actions:"));
|
|
3529
3716
|
for (const t of inProgress) {
|
|
3530
3717
|
const actions = await db.getActionsForTask(t.id);
|
|
3531
3718
|
const active = actions.filter((a) => a.status === "in_progress");
|
|
3532
3719
|
for (const a of active) {
|
|
3533
|
-
console.log(` ${
|
|
3720
|
+
console.log(` ${pc13.cyan(a.agent.padEnd(10))} \u2192 task #${t.id} ${t.slug}`);
|
|
3534
3721
|
}
|
|
3535
3722
|
}
|
|
3536
3723
|
}
|
|
@@ -3539,10 +3726,10 @@ async function runStatus(cwd2, opts) {
|
|
|
3539
3726
|
const fn = STATUS_COLOR[s.status] ?? ((x) => x);
|
|
3540
3727
|
return `${fn(s.status)}: ${s.total}`;
|
|
3541
3728
|
});
|
|
3542
|
-
console.log(
|
|
3729
|
+
console.log(pc13.dim("Tasks \u2014 ") + parts.join(pc13.dim(" | ")));
|
|
3543
3730
|
const archivedTasks = await db.getArchivedTasks();
|
|
3544
3731
|
if (archivedTasks.length > 0) {
|
|
3545
|
-
console.log(
|
|
3732
|
+
console.log(pc13.dim(`${archivedTasks.length} archived (use \`ahk task list --archived\` to view)`));
|
|
3546
3733
|
}
|
|
3547
3734
|
} finally {
|
|
3548
3735
|
await db.close();
|
|
@@ -3550,13 +3737,13 @@ async function runStatus(cwd2, opts) {
|
|
|
3550
3737
|
}
|
|
3551
3738
|
|
|
3552
3739
|
// src/commands/sync.ts
|
|
3553
|
-
import { existsSync as
|
|
3554
|
-
import { join as
|
|
3555
|
-
import
|
|
3740
|
+
import { existsSync as existsSync19, readFileSync as readFileSync11 } from "fs";
|
|
3741
|
+
import { join as join22, resolve as resolve10 } from "path";
|
|
3742
|
+
import pc14 from "picocolors";
|
|
3556
3743
|
async function runSync(cwd2, opts) {
|
|
3557
3744
|
const config = await loadConfig(cwd2);
|
|
3558
3745
|
const direction = opts.direction ?? "both";
|
|
3559
|
-
const featureListPath =
|
|
3746
|
+
const featureListPath = resolve10(join22(cwd2, config.storage.dir, "feature_list.json"));
|
|
3560
3747
|
const db = await openDB(config, cwd2);
|
|
3561
3748
|
try {
|
|
3562
3749
|
if (direction === "in" || direction === "both") {
|
|
@@ -3570,49 +3757,49 @@ async function runSync(cwd2, opts) {
|
|
|
3570
3757
|
}
|
|
3571
3758
|
}
|
|
3572
3759
|
async function syncIn(featureListPath, db, dryRun) {
|
|
3573
|
-
if (!
|
|
3574
|
-
console.log(
|
|
3760
|
+
if (!existsSync19(featureListPath)) {
|
|
3761
|
+
console.log(pc14.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
3575
3762
|
return;
|
|
3576
3763
|
}
|
|
3577
3764
|
let seeds;
|
|
3578
3765
|
try {
|
|
3579
3766
|
seeds = JSON.parse(readFileSync11(featureListPath, "utf8"));
|
|
3580
3767
|
} catch (err) {
|
|
3581
|
-
console.error(
|
|
3768
|
+
console.error(pc14.red(`Failed to parse feature_list.json: ${err}`));
|
|
3582
3769
|
process.exit(1);
|
|
3583
3770
|
}
|
|
3584
3771
|
if (dryRun) {
|
|
3585
|
-
console.log(
|
|
3772
|
+
console.log(pc14.bold("Dry run \u2014 in-sync (feature_list.json \u2192 SQLite):"));
|
|
3586
3773
|
for (const t of seeds) {
|
|
3587
3774
|
const existing = await db.getTaskBySlug(t.slug);
|
|
3588
|
-
console.log(` ${existing ?
|
|
3775
|
+
console.log(` ${existing ? pc14.dim("skip") : pc14.green("add ")} ${t.slug}`);
|
|
3589
3776
|
}
|
|
3590
3777
|
return;
|
|
3591
3778
|
}
|
|
3592
3779
|
const result = await db.syncFromFeatureList(seeds);
|
|
3593
|
-
console.log(
|
|
3780
|
+
console.log(pc14.green(`\u2713 In-sync: ${result.added} added, ${result.skipped} already existed`));
|
|
3594
3781
|
}
|
|
3595
3782
|
async function syncOut(db, cwd2, dryRun) {
|
|
3596
3783
|
if (dryRun) {
|
|
3597
3784
|
const tasks = await db.getTasks();
|
|
3598
|
-
console.log(
|
|
3785
|
+
console.log(pc14.bold("Dry run \u2014 out-sync (SQLite \u2192 feature_list.json):"));
|
|
3599
3786
|
console.log(` ${tasks.length} tasks would be written`);
|
|
3600
3787
|
return;
|
|
3601
3788
|
}
|
|
3602
3789
|
await db.writeFeatureList(cwd2);
|
|
3603
|
-
console.log(
|
|
3790
|
+
console.log(pc14.green("\u2713 Out-sync: feature_list.json updated"));
|
|
3604
3791
|
}
|
|
3605
3792
|
|
|
3606
3793
|
// src/commands/task/add.ts
|
|
3607
|
-
import * as
|
|
3608
|
-
import
|
|
3794
|
+
import * as p7 from "@clack/prompts";
|
|
3795
|
+
import pc15 from "picocolors";
|
|
3609
3796
|
async function runTaskAdd(cwd2) {
|
|
3610
|
-
|
|
3797
|
+
p7.intro(pc15.bold("agent-harness-kit \u2014 add task"));
|
|
3611
3798
|
const title = await cliFormWithRetry(
|
|
3612
3799
|
async () => {
|
|
3613
|
-
const val = await
|
|
3614
|
-
if (
|
|
3615
|
-
|
|
3800
|
+
const val = await p7.text({ message: "Task title" });
|
|
3801
|
+
if (p7.isCancel(val)) {
|
|
3802
|
+
p7.cancel("Cancelled.");
|
|
3616
3803
|
process.exit(0);
|
|
3617
3804
|
}
|
|
3618
3805
|
return val.trim();
|
|
@@ -3621,12 +3808,12 @@ async function runTaskAdd(cwd2) {
|
|
|
3621
3808
|
);
|
|
3622
3809
|
const description = await cliFormWithRetry(
|
|
3623
3810
|
async () => {
|
|
3624
|
-
const val = await
|
|
3811
|
+
const val = await p7.text({
|
|
3625
3812
|
message: "Description (what and why)",
|
|
3626
3813
|
placeholder: "Describe the task in more detail, including any relevant context or instructions for the agents."
|
|
3627
3814
|
});
|
|
3628
|
-
if (
|
|
3629
|
-
|
|
3815
|
+
if (p7.isCancel(val)) {
|
|
3816
|
+
p7.cancel("Cancelled.");
|
|
3630
3817
|
process.exit(0);
|
|
3631
3818
|
}
|
|
3632
3819
|
return val.trim();
|
|
@@ -3634,13 +3821,13 @@ async function runTaskAdd(cwd2) {
|
|
|
3634
3821
|
taskDescriptionSchema
|
|
3635
3822
|
);
|
|
3636
3823
|
const acceptance = [];
|
|
3637
|
-
|
|
3824
|
+
p7.log.info("Acceptance criteria \u2014 one per line, empty line to finish");
|
|
3638
3825
|
while (true) {
|
|
3639
|
-
const val = await
|
|
3640
|
-
if (
|
|
3826
|
+
const val = await p7.text({ message: ">", placeholder: "Criterion (or press Enter to finish)" });
|
|
3827
|
+
if (p7.isCancel(val) || !val || !val.trim()) break;
|
|
3641
3828
|
acceptance.push(val.trim());
|
|
3642
3829
|
}
|
|
3643
|
-
const spinner6 =
|
|
3830
|
+
const spinner6 = p7.spinner();
|
|
3644
3831
|
spinner6.start("Saving...");
|
|
3645
3832
|
try {
|
|
3646
3833
|
const config = await loadConfig(cwd2);
|
|
@@ -3650,28 +3837,28 @@ async function runTaskAdd(cwd2) {
|
|
|
3650
3837
|
await db.writeFeatureList(cwd2);
|
|
3651
3838
|
await db.close();
|
|
3652
3839
|
spinner6.stop("");
|
|
3653
|
-
console.log(
|
|
3654
|
-
console.log(
|
|
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");
|
|
3655
3842
|
} catch (err) {
|
|
3656
|
-
spinner6.stop(
|
|
3657
|
-
|
|
3843
|
+
spinner6.stop(pc15.red("Failed"));
|
|
3844
|
+
p7.log.error(err instanceof Error ? err.message : String(err));
|
|
3658
3845
|
process.exit(1);
|
|
3659
3846
|
}
|
|
3660
3847
|
}
|
|
3661
3848
|
|
|
3662
3849
|
// src/commands/task/done.ts
|
|
3663
3850
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3664
|
-
import { existsSync as
|
|
3665
|
-
import { resolve as
|
|
3666
|
-
import
|
|
3851
|
+
import { existsSync as existsSync20 } from "fs";
|
|
3852
|
+
import { resolve as resolve11 } from "path";
|
|
3853
|
+
import pc16 from "picocolors";
|
|
3667
3854
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3668
3855
|
const config = await loadConfig(cwd2);
|
|
3669
3856
|
if (config.health.required) {
|
|
3670
|
-
const scriptPath =
|
|
3671
|
-
if (
|
|
3857
|
+
const scriptPath = resolve11(cwd2, config.health.scriptPath);
|
|
3858
|
+
if (existsSync20(scriptPath)) {
|
|
3672
3859
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3673
3860
|
if (result.status !== 0) {
|
|
3674
|
-
console.error(
|
|
3861
|
+
console.error(pc16.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|
|
3675
3862
|
if (result.stdout) console.error(result.stdout);
|
|
3676
3863
|
if (result.stderr) console.error(result.stderr);
|
|
3677
3864
|
process.exit(1);
|
|
@@ -3684,79 +3871,79 @@ async function runTaskDone(cwd2, idOrSlug) {
|
|
|
3684
3871
|
const isId = !isNaN(parsed);
|
|
3685
3872
|
const task2 = isId ? await db.getTaskById(parsed) : await db.getTaskBySlug(idOrSlug);
|
|
3686
3873
|
if (!task2) {
|
|
3687
|
-
console.error(
|
|
3874
|
+
console.error(pc16.red(`Task not found: ${idOrSlug}`));
|
|
3688
3875
|
process.exit(1);
|
|
3689
3876
|
}
|
|
3690
3877
|
if (task2.status === "done") {
|
|
3691
|
-
console.log(
|
|
3878
|
+
console.log(pc16.dim(`Task #${task2.id} is already done.`));
|
|
3692
3879
|
return;
|
|
3693
3880
|
}
|
|
3694
3881
|
await db.updateTaskStatus(task2.id, "done");
|
|
3695
3882
|
await db.writeFeatureList(cwd2);
|
|
3696
|
-
console.log(
|
|
3883
|
+
console.log(pc16.green(`\u2713 Task #${task2.id} \u2014 ${task2.slug} marked as done`));
|
|
3697
3884
|
} finally {
|
|
3698
3885
|
await db.close();
|
|
3699
3886
|
}
|
|
3700
3887
|
}
|
|
3701
3888
|
|
|
3702
3889
|
// src/commands/task/edit.ts
|
|
3703
|
-
import * as
|
|
3704
|
-
import
|
|
3890
|
+
import * as p8 from "@clack/prompts";
|
|
3891
|
+
import pc17 from "picocolors";
|
|
3705
3892
|
async function runTaskEdit(cwd2) {
|
|
3706
|
-
|
|
3893
|
+
p8.intro(pc17.bold("agent-harness-kit \u2014 edit task"));
|
|
3707
3894
|
const config = await loadConfig(cwd2);
|
|
3708
3895
|
const db = await openDB(config, cwd2);
|
|
3709
3896
|
try {
|
|
3710
3897
|
const allTasks = await db.getTasks();
|
|
3711
3898
|
const activeTasks = allTasks.filter((t) => t.status !== "done");
|
|
3712
3899
|
if (activeTasks.length === 0) {
|
|
3713
|
-
|
|
3900
|
+
p8.log.error("No active tasks to edit.");
|
|
3714
3901
|
return;
|
|
3715
3902
|
}
|
|
3716
|
-
const taskId = await
|
|
3903
|
+
const taskId = await p8.select({
|
|
3717
3904
|
message: "Select a task to edit",
|
|
3718
3905
|
options: activeTasks.map((t) => ({
|
|
3719
3906
|
label: `#${t.id} \u2014 ${t.title} (${t.slug})`,
|
|
3720
3907
|
value: t.id
|
|
3721
3908
|
}))
|
|
3722
3909
|
});
|
|
3723
|
-
if (
|
|
3724
|
-
|
|
3910
|
+
if (p8.isCancel(taskId)) {
|
|
3911
|
+
p8.cancel("Cancelled.");
|
|
3725
3912
|
process.exit(0);
|
|
3726
3913
|
}
|
|
3727
3914
|
const task2 = await db.getTaskById(taskId);
|
|
3728
3915
|
if (!task2) {
|
|
3729
|
-
|
|
3916
|
+
p8.log.error("Task not found");
|
|
3730
3917
|
process.exit(1);
|
|
3731
3918
|
}
|
|
3732
|
-
const title = await
|
|
3919
|
+
const title = await p8.text({
|
|
3733
3920
|
message: "Title",
|
|
3734
3921
|
initialValue: task2.title
|
|
3735
3922
|
});
|
|
3736
|
-
if (
|
|
3737
|
-
|
|
3923
|
+
if (p8.isCancel(title)) {
|
|
3924
|
+
p8.cancel("Cancelled.");
|
|
3738
3925
|
process.exit(0);
|
|
3739
3926
|
}
|
|
3740
|
-
const description = await
|
|
3927
|
+
const description = await p8.text({
|
|
3741
3928
|
message: "Description (what and why)",
|
|
3742
3929
|
initialValue: task2.description ?? ""
|
|
3743
3930
|
});
|
|
3744
|
-
if (
|
|
3745
|
-
|
|
3931
|
+
if (p8.isCancel(description)) {
|
|
3932
|
+
p8.cancel("Cancelled.");
|
|
3746
3933
|
process.exit(0);
|
|
3747
3934
|
}
|
|
3748
3935
|
const currentAcceptance = await db.getTaskAcceptance(task2.id);
|
|
3749
3936
|
const newAcceptance = [];
|
|
3750
|
-
|
|
3937
|
+
p8.log.info("Acceptance criteria \u2014 edit each, empty to delete. Add new ones at the end.");
|
|
3751
3938
|
for (let i = 0; i < currentAcceptance.length; i++) {
|
|
3752
3939
|
const ac = currentAcceptance[i];
|
|
3753
|
-
const val = await
|
|
3940
|
+
const val = await p8.text({
|
|
3754
3941
|
message: `#${i + 1}/${currentAcceptance.length}`,
|
|
3755
3942
|
initialValue: ac.criterion,
|
|
3756
3943
|
defaultValue: ""
|
|
3757
3944
|
});
|
|
3758
|
-
if (
|
|
3759
|
-
|
|
3945
|
+
if (p8.isCancel(val)) {
|
|
3946
|
+
p8.cancel("Cancelled.");
|
|
3760
3947
|
process.exit(0);
|
|
3761
3948
|
}
|
|
3762
3949
|
const trimmed = val.trim();
|
|
@@ -3765,11 +3952,11 @@ async function runTaskEdit(cwd2) {
|
|
|
3765
3952
|
}
|
|
3766
3953
|
}
|
|
3767
3954
|
while (true) {
|
|
3768
|
-
const val = await
|
|
3769
|
-
if (
|
|
3955
|
+
const val = await p8.text({ message: "New acceptance criterion", placeholder: "(press Enter to finish)" });
|
|
3956
|
+
if (p8.isCancel(val) || !val || !val.trim()) break;
|
|
3770
3957
|
newAcceptance.push(val.trim());
|
|
3771
3958
|
}
|
|
3772
|
-
const spinner6 =
|
|
3959
|
+
const spinner6 = p8.spinner();
|
|
3773
3960
|
spinner6.start("Saving...");
|
|
3774
3961
|
try {
|
|
3775
3962
|
const newSlug = slugify(title);
|
|
@@ -3781,10 +3968,10 @@ async function runTaskEdit(cwd2) {
|
|
|
3781
3968
|
await db.updateTaskAcceptance(task2.id, newAcceptance);
|
|
3782
3969
|
await db.writeFeatureList(cwd2);
|
|
3783
3970
|
spinner6.stop("");
|
|
3784
|
-
console.log(
|
|
3971
|
+
console.log(pc17.green(`\u2713 Task #${task2.id} updated \u2014 ${newSlug}`));
|
|
3785
3972
|
} catch (err) {
|
|
3786
|
-
spinner6.stop(
|
|
3787
|
-
|
|
3973
|
+
spinner6.stop(pc17.red("Failed"));
|
|
3974
|
+
p8.log.error(err instanceof Error ? err.message : String(err));
|
|
3788
3975
|
process.exit(1);
|
|
3789
3976
|
}
|
|
3790
3977
|
} finally {
|
|
@@ -3794,12 +3981,12 @@ async function runTaskEdit(cwd2) {
|
|
|
3794
3981
|
|
|
3795
3982
|
// src/commands/task/list.ts
|
|
3796
3983
|
import Table2 from "cli-table3";
|
|
3797
|
-
import
|
|
3984
|
+
import pc18 from "picocolors";
|
|
3798
3985
|
var STATUS_COLOR2 = {
|
|
3799
|
-
pending: (s) =>
|
|
3800
|
-
in_progress: (s) =>
|
|
3801
|
-
done: (s) =>
|
|
3802
|
-
blocked: (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)
|
|
3803
3990
|
};
|
|
3804
3991
|
async function runTaskList(cwd2, opts) {
|
|
3805
3992
|
const config = await loadConfig(cwd2);
|
|
@@ -3816,11 +4003,11 @@ async function runTaskList(cwd2, opts) {
|
|
|
3816
4003
|
let msg = "No tasks";
|
|
3817
4004
|
if (filterStatus) msg += ` with status: ${filterStatus}`;
|
|
3818
4005
|
if (opts.archived) msg += " (archived)";
|
|
3819
|
-
console.log(
|
|
4006
|
+
console.log(pc18.dim(msg + "."));
|
|
3820
4007
|
return;
|
|
3821
4008
|
}
|
|
3822
4009
|
const table = new Table2({
|
|
3823
|
-
head: ["ID", "Slug", "Title", "Status"].map((h) =>
|
|
4010
|
+
head: ["ID", "Slug", "Title", "Status"].map((h) => pc18.bold(h)),
|
|
3824
4011
|
style: { head: [], border: [] }
|
|
3825
4012
|
});
|
|
3826
4013
|
for (const t of tasks) {
|
|
@@ -3831,7 +4018,7 @@ async function runTaskList(cwd2, opts) {
|
|
|
3831
4018
|
if (!opts.archived && !opts.includeArchived) {
|
|
3832
4019
|
const archivedTasks = await db.getArchivedTasks();
|
|
3833
4020
|
if (archivedTasks.length > 0) {
|
|
3834
|
-
console.log(
|
|
4021
|
+
console.log(pc18.dim(`${archivedTasks.length} archived task${archivedTasks.length !== 1 ? "s" : ""} (use --archived to view)`));
|
|
3835
4022
|
}
|
|
3836
4023
|
}
|
|
3837
4024
|
} finally {
|
|
@@ -3841,8 +4028,8 @@ async function runTaskList(cwd2, opts) {
|
|
|
3841
4028
|
|
|
3842
4029
|
// src/core/path-probe.ts
|
|
3843
4030
|
import { accessSync, constants, readdirSync as readdirSync3 } from "fs";
|
|
3844
|
-
import { join as
|
|
3845
|
-
import
|
|
4031
|
+
import { join as join23 } from "path";
|
|
4032
|
+
import pc19 from "picocolors";
|
|
3846
4033
|
var DEFAULT_PATHEXT = [".COM", ".EXE", ".BAT", ".CMD"];
|
|
3847
4034
|
function defaultIsExecutable(filePath) {
|
|
3848
4035
|
try {
|
|
@@ -3886,7 +4073,7 @@ function resolveOnPath(name, options = {}) {
|
|
|
3886
4073
|
}
|
|
3887
4074
|
for (const dir of dirs) {
|
|
3888
4075
|
try {
|
|
3889
|
-
if (isExecutable(
|
|
4076
|
+
if (isExecutable(join23(dir, name))) return true;
|
|
3890
4077
|
} catch {
|
|
3891
4078
|
continue;
|
|
3892
4079
|
}
|
|
@@ -3897,35 +4084,35 @@ function isExecutableOnPath(name) {
|
|
|
3897
4084
|
return resolveOnPath(name);
|
|
3898
4085
|
}
|
|
3899
4086
|
function printMissingGlobalBinaryWarning() {
|
|
3900
|
-
console.error(
|
|
3901
|
-
console.error(
|
|
3902
|
-
console.error(
|
|
3903
|
-
console.error(
|
|
3904
|
-
console.error(
|
|
3905
|
-
console.error(
|
|
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)`));
|
|
3906
4093
|
}
|
|
3907
4094
|
|
|
3908
4095
|
// src/core/update-check.ts
|
|
3909
|
-
import
|
|
4096
|
+
import pc20 from "picocolors";
|
|
3910
4097
|
var REGISTRY_URL2 = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
3911
4098
|
var TIMEOUT_MS2 = 2500;
|
|
3912
4099
|
function checkForUpdate(currentVersion) {
|
|
3913
|
-
return new Promise((
|
|
3914
|
-
const timer = setTimeout(() =>
|
|
4100
|
+
return new Promise((resolve12) => {
|
|
4101
|
+
const timer = setTimeout(() => resolve12(null), TIMEOUT_MS2);
|
|
3915
4102
|
fetch(REGISTRY_URL2).then((res) => res.json()).then((data) => {
|
|
3916
4103
|
clearTimeout(timer);
|
|
3917
4104
|
const latest = data.version;
|
|
3918
|
-
|
|
4105
|
+
resolve12(isNewer2(latest, currentVersion) ? { current: currentVersion, latest } : null);
|
|
3919
4106
|
}).catch(() => {
|
|
3920
4107
|
clearTimeout(timer);
|
|
3921
|
-
|
|
4108
|
+
resolve12(null);
|
|
3922
4109
|
});
|
|
3923
4110
|
});
|
|
3924
4111
|
}
|
|
3925
4112
|
function printUpdateMessage({ current, latest }) {
|
|
3926
4113
|
const lines = [
|
|
3927
|
-
` Update available ${
|
|
3928
|
-
` Run: ${
|
|
4114
|
+
` Update available ${pc20.dim(current)} \u2192 ${pc20.green(latest)} `,
|
|
4115
|
+
` Run: ${pc20.cyan(`pnpm i ${pkg.name}@${latest}`)} `
|
|
3929
4116
|
];
|
|
3930
4117
|
drawBox(lines);
|
|
3931
4118
|
}
|
|
@@ -3955,7 +4142,7 @@ function parsePort(raw) {
|
|
|
3955
4142
|
var updateCheck = checkForUpdate(pkg.version);
|
|
3956
4143
|
var program = new Command();
|
|
3957
4144
|
program.name("ahk").description("agent-harness-kit \u2014 CLI scaffolding for multi-agent harness systems").version(pkg.version, "-v, --version");
|
|
3958
|
-
program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
|
|
4145
|
+
program.command("init").description("Scaffold a harness interactively in the current directory").option("--name <name>", "Project name (skip prompt)").option("--provider <provider>", "AI provider: claude-code | opencode | codex-cli | grok-cli (skip prompt)").option("--docs <path>", "Docs folder path (skip prompt)").option("--tasks <adapter>", "Task adapter: local | jira | linear (skip prompt)").option("--storage-scope <scope>", "Storage scope: local | global (skip prompt)").action(async (opts) => {
|
|
3959
4146
|
await runInit(cwd, opts);
|
|
3960
4147
|
});
|
|
3961
4148
|
program.command("build").description("Regenerate AGENTS.md and provider files from agent-harness-kit.config.ts").option("--watch", "Rebuild on config changes").option("--sync", "Sync tools: frontmatter in existing .claude/agents/*.md to match current permission constants").option(
|
|
@@ -3993,7 +4180,7 @@ program.command("dashboard").description("Open web dashboard to visualize harnes
|
|
|
3993
4180
|
await runDashboard(cwd, { port: opts.port, open: opts.open });
|
|
3994
4181
|
});
|
|
3995
4182
|
var migrate = program.command("migrate").description("Migrate provider files to a different provider, or migrate harness storage (see subcommands)");
|
|
3996
|
-
migrate.command("provider").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode | codex-cli").action(async (opts) => {
|
|
4183
|
+
migrate.command("provider").description("Migrate provider-specific files to a different provider").option("--to <provider>", "Target provider: claude-code | opencode | codex-cli | grok-cli").action(async (opts) => {
|
|
3997
4184
|
await runMigrate(cwd, opts);
|
|
3998
4185
|
});
|
|
3999
4186
|
migrate.command("storage").description(
|
|
@@ -4002,19 +4189,22 @@ migrate.command("storage").description(
|
|
|
4002
4189
|
try {
|
|
4003
4190
|
await runMigrateStorage(cwd, { force: opts.force, dryRun: opts["dry-run"] });
|
|
4004
4191
|
} catch (err) {
|
|
4005
|
-
console.error(
|
|
4192
|
+
console.error(pc21.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
|
|
4006
4193
|
process.exit(1);
|
|
4007
4194
|
}
|
|
4008
4195
|
});
|
|
4009
4196
|
program.command("export").description("Export the database").option("--sql", "SQL dump").option("--json", "JSON export of tasks and actions").option("--output <path>", "Output file path (default: stdout)").action(async (opts) => {
|
|
4010
4197
|
await runExport(cwd, opts);
|
|
4011
4198
|
});
|
|
4012
|
-
program.command("reset").description("Reset/clear harness data (DB, feature list, agent files)").option("--force", "Skip confirmation prompts").option("--provider <claude-code|opencode>", "Reset agent MD files for specified provider").action(async (opts) => {
|
|
4199
|
+
program.command("reset").description("Reset/clear harness data (DB, feature list, agent files)").option("--force", "Skip confirmation prompts").option("--provider <claude-code|opencode|codex-cli|grok-cli>", "Reset agent MD files for specified provider").action(async (opts) => {
|
|
4013
4200
|
await runReset(cwd, opts);
|
|
4014
4201
|
});
|
|
4015
4202
|
program.command("doctor").description("Check lib version, agent files, and harness skills sync status").action(async () => {
|
|
4016
4203
|
await runDoctor(cwd);
|
|
4017
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
|
+
});
|
|
4018
4208
|
program.hook("preAction", () => {
|
|
4019
4209
|
if (!isLocalInstallSatisfied(cwd)) {
|
|
4020
4210
|
printLocalInstallWarning();
|